2018年力扣高頻算法面試題4動態規劃

除自身以外數組的乘積

給定長度爲 n 的整數數組 nums,其中 n > 1,返回輸出數組 output ,其中 output[i] 等於 nums 中除 nums[i] 之外其餘各元素的乘積。
說明: 請不要使用除法,且在 O(n) 時間複雜度內完成此題。
分析: 對於某一個數字,如果我們知道其前面所有數字的乘積,同時也知道後面所有的數乘積,那麼二者相乘就是我們要的結果,所以我們只要分別創建出這兩個數組即可,分別從數組的兩個方向遍歷就可以分別創建出乘積累積數組。
優化: 不用單獨的數組來保存乘積,而是直接累積到結果 res 中,我們先從前面遍歷一遍,將乘積的累積存入結果 res 中,然後從後面開始遍歷,用到一個臨時變量 right,初始化爲1,然後每次不斷累積,最終得到正確結果

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        vector<int> res(nums.size(), 1);
        for (int i = 1; i < nums.size(); ++i) {
            res[i] = res[i - 1] * nums[i - 1];
        }
        int right = 1;
        for (int i = nums.size() - 1; i >= 0; --i) {
            res[i] *= right;
            right *= nums[i];
        }
        return res;
    }
};

遞增的三元子序列

給定一個未排序的數組,判斷這個數組中是否存在長度爲 3 的遞增子序列。
數學表達式如下:
如果存在這樣的 i, j, k, 且滿足 0 ≤ i < j < k ≤ n-1,使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否則返回 false 。
說明: 要求算法的時間複雜度爲 O(n),空間複雜度爲 O(1) 。
分析: 遍歷數組,如果m1大於等於當前數字,則將當前數字賦給m1;如果m1小於當前數字且m2大於等於當前數字,那麼將當前數字賦給m2,一旦m2被更新了,說明一定會有一個數小於m2,那麼我們就成功的組成了一個長度爲2的遞增子序列,所以我們一旦遍歷到比m2還大的數,我們直接返回ture。如果我們遇到比m1小的數,還是要更新m1,有可能的話也要更新m2爲更小的值,畢竟m2的值越小,能組成長度爲3的遞增序列的可能性越大

class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int m1 = INT_MAX, m2 = INT_MAX;
        for (auto a : nums) {
            if (m1 >= a) m1 = a;
            else if (m2 >= a) m2 = a;
            else return true;
        }
        return false;
    }
};

乘積最大子序列

給定一個整數數組 nums ,找出一個序列中乘積最大的連續子序列(該序列至少包含一個數)。
分析:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int res = nums[0], mx = res, mn = res;
        for (int i = 1; i < nums.size(); ++i) 
        {
            if (nums[i] < 0) swap(mx, mn);
            mx = max(nums[i], mx * nums[i]);
            mn = min(nums[i], mn * nums[i]);
            res = max(res, mx);
        }
        return res;
    }
};

打亂數組

打亂一個沒有重複元素的數組。
C++11中,獲取隨機數的新方法default_random_engine,使用方法

class Solution {
public:
    vector<int>ori;
    vector<int>cur;
    int n;
    default_random_engine e;
    Solution(vector<int>& nums) {
        ori=nums;
        cur=nums;
        n=nums.size();
    }
    
    vector<int> reset() {
        return ori;
    }
    
    vector<int> shuffle() {
        for (int i = 0; i < n; ++i) {
            int j = (e() % (n - i)) + i;
            swap(cur[i], cur[j]);
        }
        return cur;
    }
};

矩陣中的最長遞增路徑

給定一個整數矩陣,找出最長遞增路徑的長度。
對於每個單元格,你可以往上,下,左,右四個方向移動。 你不能在對角線方向上移動或移動到邊界外(即不允許環繞)。

class Solution {
public:
    int m, n; 
    vector<vector<int>> memo;
    int dfs(vector<vector<int>>& matrix, int x, int y) {
        if(memo[x][y] != -1)
            return memo[x][y];
        int ret = 1;        
        if(x>0 && matrix[x-1][y]>matrix[x][y]) 
            ret = max(ret, 1 + dfs(matrix, x-1, y));
        if(x<m-1 && matrix[x+1][y]>matrix[x][y])
            ret = max(ret, 1 + dfs(matrix, x+1, y));
        if(y>0 && matrix[x][y-1]>matrix[x][y])
            ret = max(ret, 1 + dfs(matrix, x, y-1));
        if(y<n-1 && matrix[x][y+1]>matrix[x][y])
            ret = max(ret, 1 + dfs(matrix, x, y+1));
        memo[x][y] = ret;
        return ret;
    }
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        m = matrix.size();
        if(m == 0) return 0;
        n = matrix[0].size();
        memo.resize(m);
        int ans = 1;
        for(int i = 0; i < m; ++i) memo[i].resize(n, -1);
        for(int i = 0; i < m; ++i) 
            for(int j = 0; j < n; ++j) 
                ans=max(ans,dfs(matrix, i, j));
        return ans;
    }
};

零錢兌換

給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。
分析:dp[i]表示錢數爲i時的最小硬幣數

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int>dp(amount+1,amount+1);
        dp[0]=0;
        for(int i=1;i<=amount;i++)
        {
            for(int j=0;j<coins.size();j++)
            {
                if(coins[j]<=i)
                    dp[i]=min(dp[i],dp[i-coins[j]]+1);
            }
        }
        
        return dp[amount]>amount?-1:dp[amount];
    }
};

最長連續序列

給定一個未排序的整數數組,找出最長連續序列的長度。
要求算法的時間複雜度爲 O(n)。

class Solution {
public:
    int longestConsecutive(vector<int> &num) {
        unordered_map<int,int> len;
        int max=0;
        for(auto i:num)
        {
            if(len[i]==0)//避免重複元素
            {
                int l=len[i-1],r=len[i+1];
                len[i]=l+r+1;
                len[i+r]=l+r+1;
                len[i-l]=l+r+1;
                max=max>len[i]?max:len[i];
            }
        }
        return max;
    }
};

最長上升子序列

給定一個無序的整數數組,找到其中最長上升子序列的長度。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size()==0)return 0;
        vector<int>dp(nums.size(),1);
        for(int i=1;i<nums.size();i++)
        {
            for(int j=i-1;j>=0;j--)
            {
                if(nums[j]<nums[i])
                {
                    dp[i]=max(dp[j]+1,dp[i]);
                }
            }
        }
        sort(dp.begin(),dp.end());
        return dp[nums.size()-1];
    }
};

lower_bound 返回數組中第一個不小於指定值的元素,跟上面的算法類似,還需要一個一維數組v,然後對於遍歷到的 nums 中每一個元素,找其 lower_bound,如果沒有 lower_bound,說明新元素比一維數組的尾元素還要大,直接添加到數組v中,跟解法二的思路相同了。如果有 lower_bound,說明新元素不是最大的,將其 lower_bound 替換爲新元素,這個過程跟算法二的二分查找法的部分實現相同功能,最後也是返回數組v的長度,注意數組v也不一定是真實的 LIS.

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> v;
        for (auto a : nums) {
            if (find(v.begin(), v.end(), a) != v.end()) continue;
            auto it = upper_bound(v.begin(), v.end(), a);
            if (it == v.end()) v.push_back(a);
            else *it = a;
        }
        return v.size();
    }
};

完全平方數

給定正整數 n,找到若干個完全平方數(比如 1, 4, 9, 16, …)使得它們的和等於 n。你需要讓組成和的完全平方數的個數最少。
看到這個題的第一眼就知道用動態規劃做,因爲貪心的話樣例就過不了,但是腦子不夠用寫不出來狀態表達式dp[i]=min(dp[i],dp[i-j*j]+1)。

class Solution {
public:
    int numSquares(int n) {
        int dp[n+1]={0};
        fill(dp,dp+n+1,INT_MAX);
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j*j<=i;j++)
            {
                dp[i]=min(dp[i],dp[i-j*j]+1);
            }
        }
        return dp[n];
    }
};

還有一種方法就是四平方和定理,第一次聽說這個定理完全是懵的。
根據四平方和定理,任意一個正整數均可表示爲4個整數的平方和,其實是可以表示爲4個以內的平方數之和,那麼就是說返回結果只有 1,2,3 或4其中的一個,首先我們將數字化簡一下,由於一個數如果含有因子4,那麼我們可以把4都去掉,並不影響結果,比如2和8,3和12等等,返回的結果都相同,讀者可自行舉更多的栗子。還有一個可以化簡的地方就是,如果一個數除以8餘7的話,那麼肯定是由4個完全平方數組成,這裏就不證明了,因爲我也不會證明,讀者可自行舉例驗證。那麼做完兩步後,一個很大的數有可能就會變得很小了,大大減少了運算時間,下面我們就來嘗試的將其拆爲兩個平方數之和,如果拆成功了那麼就會返回1或2,因爲其中一個平方數可能爲0. (注:由於輸入的n是正整數,所以不存在兩個平方數均爲0的情況)。注意下面的 !!a + !!b 這個表達式,可能很多人不太理解這個的意思,其實很簡單,感嘆號!表示邏輯取反,那麼一個正整數邏輯取反爲0,再取反爲1,所以用兩個感嘆號!!的作用就是看a和b是否爲正整數,都爲正整數的話返回2,只有一個是正整數的話返回1.

class Solution {
public:
    int numSquares(int n) {
        while (n % 4 == 0) n /= 4;
        if (n % 8 == 7) return 4;
        for (int a = 0; a * a <= n; ++a) {
            int b = sqrt(n - a * a);
            if (a * a + b * b == n) {
                return !!a + !!b;
            }
        }
        return 3;
    }
};

雞蛋掉落

你將獲得 K 個雞蛋,並可以使用一棟從 1 到 N 共有 N 層樓的建築。
每個蛋的功能都是一樣的,如果一個蛋碎了,你就不能再把它掉下去。
你知道存在樓層 F ,滿足 0 <= F <= N 任何從高於 F 的樓層落下的雞蛋都會碎,從 F 樓層或比它低的樓層落下的雞蛋都不會破。
每次移動,你可以取一個雞蛋(如果你有完整的雞蛋)並把它從任一樓層 X 扔下(滿足 1 <= X <= N)。
你的目標是確切地知道 F 的值是多少。
無論 F 的初始值如何,你確定 F 的值的最小移動次數是多少?

class Solution {
public:
    int calcMaximumCoverage(int iTime, int K)
    {
        if (iTime == 1) return 2;
        if (K == 1) return iTime + 1;
        return calcMaximumCoverage(iTime - 1, K - 1) + calcMaximumCoverage(iTime - 1, K);
    }

    int superEggDrop(int K, int N)
    {
        int ans = 1;
        while (calcMaximumCoverage(ans, K) < N + 1)
        {
            ++ans;
        }
        return ans;
    }

};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章