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;
    }

};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值