两道经典面试算法题2020-3-20(打牌,最长上升字符串拼接)

题目一

题意

有一叠扑克牌,每张牌介于1和10之间

有四种出牌方法:

  • 单出一张
  • 出两张相同的牌(对子)
  • 出五张顺子(如12345)
  • 出三连对子(如112233)

给10个数,表示1-10每种牌有几张,问最少要多少次能出完

思路

暴力+回溯,从最小的牌开始出,分别判断四种情况能不能出,若能出,则去除掉出的牌,变成问题模型相同,规模更小的子问题求解。

card数组长度为10,card[i]表示牌号为"i+1"的牌的数量。

 

//打牌
public int Poker(int[] cards){
    return subPoker(cards,0);
}

private int subPoker(int[] cards, int k){
    int ans = Integer.MAX_VALUE;
    if (k >= cards.length) {
        return 0;
    }
    //当前牌出完,出下一张
    else if (cards[k] == 0){
        return subPoker(cards,k+1);
    }
    //出连对
    if (k <= cards.length - 3 && cards[k] >= 2 && cards[k+1] >= 2 && cards[k+2] >=2){
        cards[k] -= 2;
        cards[k+1] -= 2;
        cards[k+2] -= 2;
        ans = Math.min(1+subPoker(cards,k),ans);
        cards[k] += 2;
        cards[k+1] += 2;
        cards[k+2] += 2;
    }
    //出顺子
    if (k <= cards.length - 5 && cards[k] >= 1 && cards[k+1] >= 1 && cards[k+2] >=1 && cards[k+3] >= 1 && cards[k+4] >= 1){
        cards[k] -= 1;
        cards[k+1] -= 1;
        cards[k+2] -= 1;
        cards[k+3] -= 1;
        cards[k+4] -= 1;
        ans = Math.min(1+subPoker(cards,k),ans);
        cards[k] += 1;
        cards[k+1] += 1;
        cards[k+2] += 1;
        cards[k+3] += 1;
        cards[k+4] += 1;
    }
    //出对子
    if (cards[k] >= 2){
        cards[k] -= 2;
        ans = Math.min(1+subPoker(cards,k),ans);
        cards[k] += 2;
    }
    //出单牌
    if (cards[k] >= 1){
        cards[k] -= 1;
        ans = Math.min(1+subPoker(cards,k),ans);
        cards[k] += 1;
    }

    return ans;

}

 

这种方法是暴力求解遍历所有情况,估算时间复杂度应该是4^n(n为总牌数),考虑可否剪枝,在本题中,剪枝可以从能出xx牌型,则不可能出xx牌型出发,根据牌型优先级考虑剪枝。

首先根据相关性考虑

  • 情况1(能出对子则不出单牌),显然不合理,打过牌都知道[2,1,1,1,1]。
  • 情况2(能出顺子则不出单牌),反例[1,2,2,2,1],若出顺子,则需要4手才能把牌打完,出单牌+连对+单牌只需要3手,不合理。
  • 情况3(能出连对则不出单牌),反例[3,2,2,2,2],若出连对,则需要4手才能把牌打完,出单排+顺子+顺子只需要3手,不合理。
  • 情况4(能出连对则不出对子),反例[4,2,2,2,2],若出连对,则需要4手才能把牌打完,出对子+顺子+顺子只需要3手,不合理。

总体可以看出,牌型之间的优先级关联较弱,而从改变出牌顺序(不从最小的牌开始出,从最多的牌开始考虑),则会增加状态转移情况(考虑顺子和连对要往哪个方向),也不行。

根据本题题型来看,真实情况下牌数应该不会太多(结合实际场景),所以暂时想到的方法如上,后续有优化再更新编辑此处。

思路二

动态规划,可以看出上述思路解决的子问题重复度是非常非常非常高的,因此可以考虑用动态规划来实现。

边界状态集合不难找,但是此题状态转换太多,而且状态空间及其庞大,所以要定义很大的dp数组来存状态,当n变得很大的时候,内存占用会过多,但是动态规划本身就是空间换时间的一种算法。

 

题目二

题意

首先定义上升字符串,s[i] >= s[i-1],比如aaa,abc是,acb不是

给n个上升字符串,选择任意个拼起来,问能拼出来的最长上升字符串长度。

思路

动态规划,创建一个长度为26的dp[]数组,dp[i]表示以字符'a'+i结尾的最长上升字符串长度。

用一个桶,将所有字符串依据字符串末尾字符分成26份装入桶中。

对dp[i]的求法是,从第i个桶中拿出所有字符串s,设s的字符串长度为l,开头字符为c,则遍历0~c-'a'的dp数组,加上l,则构成一种情况。

对于开头字符和结尾字符相同的字符串,需要特殊处理一下,详见代码

 

//上升字符串最大连接,返回最大连接长度
public int maxLengthConcat(String[] str){
    int ans = 0;
    int[] dp = new int[26];
    int[] add = new int[26];
    List<ArrayList<String>> l = new ArrayList<ArrayList<String>>();
    for (int i = 0; i < 26; i++) {
        l.add(new ArrayList<String>());
    }

    //用桶的思想,将以(int)x结尾的字符串装到相应的桶里
    for (int i = 0; i < str.length; i++) {
        //字符结尾
        int j = str[i].charAt(str[i].length()-1) - 'a';
        //特殊情况,以x开头并以x结尾,不装入,将长度加到add数组,可以视为
        //所有以x结尾的字符串的长度,默认+add[x]
        if (str[i].charAt(0) - 'a' == j) {
            add[j] += str[i].length();
        }
        else {
            l.get(j).add(str[i]);
        }
    }

    //初始化以'a'为结尾的最长长度
    for (int i = 0; i < l.get(0).size(); i++) {
        dp[0] = Math.max(l.get(0).get(i).length(),dp[0]);
    }

    //从'a'开始更新
    for (int i = 0; i < 26; i++) {
        if (l.get(i).size() == 0 && add[i] > 0){
            for (int j = 0; j < i; j++) {
                dp[i] = Math.max(dp[i],dp[j]);
            }
        }
//遍历以'a'+i 为结尾的字符串
for (int j = 0; j < l.get(i).size(); j++) { String s = l.get(i).get(j); int len = s.length(); int c = s.charAt(0) - 'a'; for (int k = 0; k <= c; k++) { dp[i] = Math.max(dp[i], dp[k] + len); } } dp[i] += add[i]; } for (int i = 0; i < 26; i++) { ans = ans > dp[i] ? ans : dp[i]; } return ans; }

 

虽然装在不同的桶里,但实际上每个字符串遍历一次,每次遍历需要对前面的dp数组进行遍历,而dp数组的长度固定为26,所以该方法时间复杂度为O(n)。

PS:看到牛客网有评论说这样会超时,但是从题目上看无论如何都想不到跟二分的关系,那么就基本不可能是O(logn),所以个人认为O(n)已经是最优解法,有想到优化再更新。

PS2:有其他评论说可以按照字符串的末尾字符给字符串排序,但是排序的时间复杂度就超出了O(n),得不偿失,个人认为没必要排序,但是排序可以节省桶的空间,算是时间换空间吧。

由于本人没有真实参加面试,以上代码均没通过官方检测,不保证完全正确,仅供参考,有问题欢迎指出。

 

 

   

 

posted @ 2020-03-20 13:23  咕咕刘三刀  阅读(1339)  评论(0编辑  收藏  举报