Monday, June 3, 2019

Leetcode 752 Open the Lock

题目原文

You have a lock in front of you with 4 circular wheels. Each wheel has 10 slots: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'. The wheels can rotate freely and wrap around: for example we can turn '9' to be '0', or '0' to be '9'. Each move consists of turning one wheel one slot.
The lock initially starts at '0000', a string representing the state of the 4 wheels.
You are given a list of deadends dead ends, meaning if the lock displays any of these codes, the wheels of the lock will stop turning and you will be unable to open it.
Given a target representing the value of the wheels that will unlock the lock, return the minimum total number of turns required to open the lock, or -1 if it is impossible.
Example 1:
Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output: 6
Explanation:
A sequence of valid moves would be "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202".
Note that a sequence like "0000" -> "0001" -> "0002" -> "0102" -> "0202" would be invalid,
because the wheels of the lock become stuck after the display becomes the dead end "0102".
Example 2:
Input: deadends = ["8888"], target = "0009"
Output: 1
Explanation:
We can turn the last wheel in reverse to move from "0000" -> "0009".
Example 3:
Input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output: -1
Explanation:
We can't reach the target without getting stuck.
Example 4:
Input: deadends = ["0000"], target = "8888"
Output: -1
Note:
  1. The length of deadends will be in the range [1, 500].
  2. target will not be in the list deadends.
  3. Every string in deadends and the string target will be a string of 4 digits from the 10,000 possibilities '0000' to '9999'.

题意分析

转自 : here
从初始状态“0000”开始转动,每次只能将其中一位转动一格,可正转可反转,同时deadends中有多个状态,如果转到该状态则不能继续,也即必不可转到目标结果。求问转到目标结果的最小次数。

解法分析

本题需要找到一个最小次数,通常需要运用BFS,在BFS中加入剪枝,并且对于到过的状态建立unordered_set,通过上述方法减少遍历次数。C++代码如下:
class Solution {
public:
   /* static bool isEnd(string a,vector<string>& deadends){
        for(auto k:deadends){
            if(a==k)
                return 1;
        }
        return 0;
    }*/
    int openLock(vector<string>& deadends, string target) {
        queue<string> myQue;
        unordered_set<string> dde(deadends.begin(),deadends.end());
        if(dde.count("0000"))
            return -1;
        myQue.push("0000");
        int queSize=1;
        int layer=0;
        int i,j;
        string temp,temp1;
        unordered_set<string> mySet;
        mySet.insert("0000");
        while(!myQue.empty()){
            layer++;
            queSize=myQue.size();
            for(i=0;i<queSize;i++){
                temp=myQue.front();
                for(j=0;j<4;j++){
                    temp1=temp;//add
                    if(temp1[j]=='9')
                        temp1[j]='0';
                    else
                        temp1[j]+=1;
                    if(temp1==target)
                        return layer;
                    if(!dde.count(temp1)&&!mySet.count(temp1)){            
                            myQue.push(temp1);
                            mySet.insert(temp1);                            
                    }
                    temp1=temp;//sub
                    if(temp1[j]=='0')
                        temp1[j]='9';
                    else
                        temp1[j]-=1;
                    if(temp1==target)
                        return layer;
                    if(!dde.count(temp1)&&!mySet.count(temp1)){
                            myQue.push(temp1);
                            mySet.insert(temp1);                   
                    }
                }
                myQue.pop(); 
            }
        }
        return -1;  
    }
};
上述代码不超时的关键除了剪枝和记录去过的状态以外,还在于检测一个状态是否deadend的方法,如果采用每次都遍历deadend一遍的方法则会超时,如果将deadend做成一个unordered_set,则可以是查询复杂度从O(n)降到O(1)。


Sunday, June 2, 2019

Leetcode 718 Maximum Length of Repeated Subarray

题目原文

Given two integer arrays A and B, return the maximum length of an subarray that appears in both arrays.
Example 1:
Input:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
Output: 3
Explanation: 
The repeated subarray with maximum length is [3, 2, 1].
Note:
  1. 1 <= len(A), len(B) <= 1000
  2. 0 <= A[i], B[i] < 100
转自 : here

题意分析

给定两个长度不一定相等的整型数组,找出他们间相同子数组的最大长度。

解法分析

此题采用动态规划方法,关键是找到最优子结构,首先我想到的是用bothLen[i][j]表示以A[i],B[j]结尾的数组的相同子数组的最长长度,但是这样做不能得到最优子结构,因此用bothLen[i][j]表示以A[i],B[j]结尾的相同子数组的长度,bothLen[i][j]=(A[i]==B[j])?bothLen[i-1][j-1]+1:0,C++代码如下:
class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        vector<vector<int>> bothLen(A.size()+1,vector<int>(B.size()+1));
        int maxLen=0;
        for(int i=1;i<=A.size();i++){
            for(int j=1;j<=B.size();j++){
                bothLen[i][j]=(A[i-1]==B[j-1])?bothLen[i-1][j-1]+1:0;
                maxLen=max(bothLen[i][j],maxLen);
            }     
        }
        return maxLen;  
    }
};
为了减少空间消耗,可以将二维vector转化为一维,但是j的遍历方向需要改变。
class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        vector<int> bothLen(B.size()+1,0);
        int maxLen=0;
        for(int i=1;i<=A.size();i++){
            for(int j=B.size();j>0;j--){
                bothLen[j]=(A[i-1]==B[j-1])?bothLen[j-1]+1:0;
                maxLen=max(bothLen[j],maxLen);
            }     
        }
        return maxLen;  
    }
};
这里bothLen[j]表示了bothLen[j][i],bothLen[j-1]表示了bothLen[j-1][i-1],为了达到这个效果j需要从大往小变,以免覆盖i-1时的值。因此对于一维数组的递归式,如a[i]=a[i-1]+1,这种可以用一个变量表示,而a[i]=a[i-1]+a[i-2],可以用两个变量表示,二维如a[i][j]=a[i][j-1]可以用a[i]表示,而本题这种需要通过改变j的遍历方向减少所需空间。当然也可以将将二维a[i][j]变为a[2][j],第一维大小固定为2.

Saturday, June 1, 2019

[LeetCode] Game of Life 生命游戏

 solve it in-place

from here

每一个位置有两种状态,1为活细胞,0为死细胞;现在我们可以丰富每一个位置上的信息,让它能表示四种状态:
状态0: 死细胞转为死细胞
状态1: 活细胞转为活细胞
状态2: 活细胞转为死细胞
状态3: 死细胞转为活细胞

最后我们对所有状态对2取余,那么状态0和2就变成死细胞,状态1和3就是活细胞,这样可以获得一次转化的结果。


class Solution {
public:
    void gameOfLife(vector<vector<int> >& board) {
        int m = board.size(), n = m ? board[0].size() : 0;
        int dx[] = {-1, -1, -1, 0, 1, 1, 1, 0};
        int dy[] = {-1, 0, 1, 1, 1, 0, -1, -1};
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                int cnt = 0;
                for (int k = 0; k < 8; ++k) {
                    int x = i + dx[k], y = j + dy[k];
                    if (x >= 0 && x < m && y >= 0 && y < n && (board[x][y] == 1 || board[x][y] == 2)) {
                        ++cnt;
                    }
                }
                if (board[i][j] && (cnt < 2 || cnt > 3)) board[i][j] = 2;
                else if (!board[i][j] && cnt == 3) board[i][j] = 3;
            }
        }
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                board[i][j] %= 2;
            }
        }
    }
};

Leetcode 121 Best Time to Buy and Sell Stock

题目原文

Say you have an array for which the ith element is the price of a given stock on day i.
If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.
Example 1:
Input: [7, 1, 5, 3, 6, 4]
Output: 5

max. difference = 6-1 = 5 (not 7-1 = 6, as selling price needs to be larger than buying price)
Example 2:
Input: [7, 6, 4, 3, 1]
Output: 0

In this case, no transaction is done, i.e. max profit = 0.

转自:here

题意分析

本题是一个简单的求最优解问题,只能买卖一次股票,求最大收益,当卖的时候的价格比买的时候高,收益为0。

解法分析

本题采用动态规划方法求解。如果用暴力方法,显而易见算法复杂度为O(n^2),所以一般用动态规划该方法复杂度变为o(n),只有一个for循环,也就是通过遍历一次原序列就能得到解。首先分析最优子结构,令从左开始的一个子序列中最小元素为a,下一个元素为b,如果b比a小,则算上b后的序列的最小元素就是b,否则仍是a;如果b比a大,则求其差值,与Pro比较,如果差值大于pro,则修改pro的值,否则pro的值不变。这是一个简单的自底向上的动态规划问题。C++代码如下:
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        if(n==0||n==1)
            return 0;
        int pro=0,min=prices[0],i,temp;
        for(i=1;i<=n-1;i++){
            if(prices[i]>min){
                temp=prices[i]-min;
                if(temp>pro)
                    pro=temp;
            }
            else
                min=prices[i];
        }
        return pro;   
    }
};
由于只有一个for循环,所以复杂度为O(n),注意对空输入和只有一个元素的输入的处理,都应输出0。

Another way, from here


这道题求进行一次交易能得到的最大利润。如果用brute force的解法就是对每组交易都看一下利润,取其中最大的,总用有n*(n-1)/2个可能交易,所以复杂度是O(n^2)。
很容易感觉出来这是动态规划的题目,其实跟Maximum Subarray非常类似,用“局部最优和全局最优解法”。思路是维护两个变量,一个是到目前为止最好的交易,另一个是在当前一天卖出的最佳交易(也就是局部最优)。递推式是local[i+1]=max(local[i]+prices[i+1]-price[i],0), global[i+1]=max(local[i+1],global[i])。这样一次扫描就可以得到结果,时间复杂度是O(n)。而空间只需要两个变量,即O(1)。代码如下:
public int maxProfit(int[] prices) {
    if(prices==null || prices.length==0)
        return 0;
    int local = 0;
    int global = 0;
    for(int i=0;i<prices.length-1;i++)
    {
        local = Math.max(local+prices[i+1]-prices[i],0);
        global = Math.max(local, global);
    }
    return global;
}
这种题目的解法非常经典,不过是比较简单的动态规划。这道题目还有两个变体,Best Time to Buy and Sell Stock IIBest Time to Buy and Sell Stock III,虽然题目要求比较像,但是思路却变化比较大,Best Time to Buy and Sell Stock II可以交易无穷多次,思路还是比较不一样,而Best Time to Buy and Sell Stock III则限定这道题交易两次,其实还可以general到限定交易k次,会在这道题目中仔细讲解,有兴趣的朋友可以看看哈。

Leetcode 122 Best Time to Buy and Sell Stock II

题目原文

Say you have an array for which the ith element is the price of a given stock on dayi.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

转自:here

题意分析

本题与121题同为买卖问题,不同的是121只能买卖一次,而本题可以买卖多次,但每次买必须在卖过之后。121题采用简单的自底向上动态规划方法解,本题将采用贪心策略。

解法分析

本题是一个最优化问题,采用动态规划方法有点杀鸡焉用宰牛刀,本题我采用贪心策略。使用贪心策略要考虑两点,一是贪心选择,二是最优子结构。首先看贪心选择,这里贪心选择为一次贪心买卖。对于买入的时机,如果下一天的值更小,则选择下一天买,这样肯定能比在前一天买获得更多收益,如果后一天的值大于前一天买入值了,则开始考虑是否卖出,遍历剩下的天数,直到数值开始下降,在下降的前一天卖出,这样一次贪心的买卖完成,剩下的天数为子问题,具有最优子结构。C++代码如下:
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int i;
        auto n=prices.size();
        if(n==0||n==1)
            return 0;
        int maxPro=0;
        int buy=prices[0],sell;
        for(i=0;i<n;i++){
            buy=prices[i];
            if(prices[i+1]<=buy)
                buy=prices[i+1];  
            else{
                while((prices[i]<=prices[i+1])&&(i+1)<n)
                    i++;
                sell=prices[i];
                maxPro+=(sell-buy);
            }   
        }
        return maxPro;      
    }
};
算法复杂度为O(n),用buy和sell来保存每次买卖的价格。

Thursday, May 30, 2019

Best Time to Buy and Sell Stock IV

另一种动态规划解法:(参考 herehere)

buy[i][j]:第i天处于买入股票状态,同时从开始到今天可最多进行j次交易时的最大利润
sold[i][j]:第i天处于卖出股票状态,同时从开始到今天可最多进行j次交易时的最大利润

对于buy[i][j],它的更新有两种情况:
一种是第i天没有买入股票,则buy[i][j]=buy[i-1][j]
一种是第i天买入股票,buy[i][j]=sold[i-1][j-1]-prices[i]

因此,
buy[i][j]=max(buy[i-1][j], sold[i-1][j-1]-prices[i])

对于sold[i][j],它的更新有两种情况:
一种是第i天没有卖出股票,则sold[i][j]=sold[i-1][j]
一种是第i天有卖出股票,sold[i][j]=buy[i-1][j]+prices[i]

因此,
sold[i][j]=max(sold[i-1][j], buy[i-1][j]+prices[i])

因此总的状态方程为:
buy[i][j]=max(buy[i-1][j], sold[i-1][j-1]-prices[i])
sold[i][j]=max(sold[i-1][j], buy[i-1][j]+prices[i])


发现什么问题了没有?
第 i 天的结果只与第 i - 1 天的结果有关,与 i - 1 之前的天数完全无关!
既然如此,更新只需要用到前一天的数据,可以考虑转为一维dp:

buy[j]=max(buy[j], sold[j-1]-prices[i])
sold[j]=max(sold[j], buy[j]+prices[i])


注意注意,前方高能!
我们看到第 i 天的buy[j]需要用到前一天的sold[j-1],但是如果我们从j=0开始遍历的话,此时得到的sell[j-1]已经变成了第 i 天的sold[j-1]了啊。

解决方法
从j=k开始遍历到1,那么修改buy[j]和sold[j]之时,就能保证sold[j-1]还是前一天的值
---------------------

同是在计算sold[j]时,我们需要用到buy[i-1][j],但是转为一维dp后,因为我们先计算了buy[j],所以此时的 buy[j] 实际上是 buy[i][j],这样就与我们需要的不符。

解决办法:在更新buy[j]前,把它的值先保存到一个临时变量中。
但是在测试中发现,不用这样做,也不影响结果。原因分析如下
1)如果buy[i][j]==buy[i-1][j],则这样当然不会影响结果。
2)如果buy[i][j]==sold[i-1][j-1]-prices[i],这意味着
sold[i-1][j-1]-prices[i]>=buy[i-1][j]
<=>  sold[i-1][j-1]>=buy[i-1][j]+prices[i]   (*)
又因为
sold[i][j]=max(sold[i-1][j], buy[i-1][j]+prices[i]),
 那么,如果用 buy[i][j]来代替 buy[i-1][j]计算,buy[i-1][j]+prices[i]=sold[i-1][j-1]-prices[i]+prices[i]=sold[i-1][j-1],
显然 sold[i-1][j]>=sold[i-1][j-1]
根据(*), sold[i-1][j-1]>=buy[i-1][j]+prices[i]
这就是说,不论 buy[j] 值正确与否,sold[i][j]总是等于sold[i-1][j],故也不会影响结果。


另外一种方法是把sold在buy前面计算:
总的状态方程为:
sold[i][j]=max(sold[i-1][j], buy[i-1][j]+prices[i])
buy[i][j]=max(buy[i-1][j], sold[i-1][j-1]-prices[i])

==》
sold[j]=max(sold[j], buy[j]+prices[i])
buy[j]=max(buy[j], sold[j-1]-prices[i])


    int maxProfit(int k, vector<int>& prices) {
        if(prices.size() ==0) return 0;
        int len = prices.size(), ans =0;
        if(k >= len/2)
        {
            for(int i = 1; i < len; i++)
                if((prices[i]-prices[i-1])>0) ans += prices[i]-prices[i-1];
            return ans;
        }
        vector<int> buy(len+1, INT_MIN), sold(len+1, 0);
        for(auto val: prices)
        {
            for(int j =k; j >0; j--)
            {
                int tmp =buy[j];
                buy[j] = max(sold[j-1]-val, buy[j]);
                sold[j] = max(tmp+val, sold[j]);
            }
        }
        return sold[k];
    }



Leetcode 714 Best Time to Buy and Sell Stock with Transaction Fee

题目原文
Your are given an array of integers prices, for which the i-th element is the price of a given stock on day i; and a non-negative integer fee representing a transaction fee.

You may complete as many transactions as you like, but you need to pay the transaction fee for each transaction. You may not buy more than 1 share of a stock at a time (ie. you must sell the stock share before you buy again.)

Return the maximum profit you can make.

Example 1:

Input: prices = [1, 3, 2, 8, 4, 9], fee = 2
Output: 8
Explanation: The maximum profit can be achieved by:
Buying at prices[0] = 1Selling at prices[3] = 8Buying at prices[4] = 4Selling at prices[5] = 9The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
Note:

0 < prices.length <= 50000.0 < prices[i] < 50000.0 <= fee < 50000.
题意分析
给定一组某一stock在每一天的价格,买卖次数不限,每次买入必须在卖出之后,且每次卖出时都需要fee的手续费,求解最大的收益。

解法分析 from here
本题采用两种方法解

动态规划
贪心算法
动态规划

对于第i天的最大收益,应分成两种情况,一是该天结束后手里没有stock,可能是保持前一天的状态也可能是今天卖出了,此时令收益为cash;二是该天结束后手中有一个stock,可能是保持前一天的状态,也可能是今天买入了,用hold表示。由于第i天的情况只和i-1天有关,所以用两个变量cash和hold就可以,不需要用数组。C++代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int cash=0;//the maxPro you have if you don't have a stock that day
        int hold=-prices[0];//the maxPro you have if you have a stock that day, if you have a stock
                                     // the  first day,hold=-prices[0]
        int i;
        for(i=1;i<prices.size();i++){
            cash=max(cash,hold+prices[i]-fee);//cash in day i is the maxvalue of cash in day i-1 or you //sell your stock
            hold=max(hold,cash-prices[i]);
        }
        return cash;   
    }
};
这里需要注意cash和hold的初始值,最终输出cash,因为最后一天的情况一定是手里没有stock的。
贪心算法

贪心选择的关键是找到一个最大后是不是能够卖掉stock,重新开始寻找买入机会。比如序列1 3 2 8,如果发现2小于3就完成交易买1卖3,此时由于fee=2,(3-1-fee)+(8-2-fee)<(8-1-fee),所以说明卖早了,令max是当前最大price,当(max-price[i]>=fee)时可以在max处卖出,且不会存在卖早的情况,再从i开始重新寻找买入机会。c++代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int profit=0;
        int curProfit=0;
        int minP=prices[0];
        int maxP=prices[0];
        int i;
        for(i=1;i<prices.size();i++){
            minP=min(minP,prices[i]);
            maxP=max(maxP,prices[i]);
            curProfit=max(curProfit,prices[i]-minP-fee);
            if((maxP-prices[i])>=fee){//can just sell the stock at maxP day.
                profit+=curProfit;
                curProfit=0;
                minP=prices[i];
                maxP=prices[i];
            }
        }
        return profit+curProfit;//the last trade have to be made if there is some profit
    }
};
curProfit记录了当前一次交易能得到的最大收益,只有当maxP-prices[i]>=fee时,才将curProfit累加到总的收益中。最后一次交易不需要考虑是否早卖了,所以直接累加最后一次的curProfit。


---------------------
作者:Zarlove
来源:CSDN
原文:https://blog.csdn.net/zarlove/article/details/78323469
版权声明:本文为博主原创文章,转载请附上博文链接!