编程语言
首页 > 编程语言> > 2021.2.8 - 2021.2.9 算法练习

2021.2.8 - 2021.2.9 算法练习

作者:互联网

837. 新21点

根据题意,分析爱丽丝持有卡牌的分数不同时,分数不超过N的概率。因为当爱丽丝手上卡牌的分数大于等于K时,爱丽丝就不会再抽卡,因此爱丽丝手上卡牌的分数就在 [0,K + W - 1]这个区间。那么,对于爱丽丝不会再抽卡的区间,也就是 [K, K + W - 1],由于不会再抽卡,因此分数不会超过N的概率就是固定的 。如果N小于K,那么爱丽丝手中持有卡牌的分数在 [K, K + W - 1] 这个区间内,所求概率一定是0。同样的,如果N大于K + W - 1,那么所求概率一定是1。如果N在[K, K + W - 1]这个区间内,那么小于等于N时,概率为1,大于N时,概率为0。

那么,现在已经明确了爱丽丝不会抽卡时,持有分数不超过N的概率了。那么对于还需要抽卡时的情况就需要再进行讨论。例如对于持有分数为K - 1时,此时爱丽丝还会继续抽卡,而抽到的每一张卡是等概率的,也就是说,抽完卡后,爱丽丝持有分数就可能是K,K + 1,K + 2,K + 3 …… K + W -1,而概率都是相同的,因此当爱丽丝持有分数为K - 1时,最后分数不超过N的概率就是K,K + 1,K + 2,K + 3 …… K + W -1的平均概率。

所以,假设爱丽丝持有分数为i,最后分数不超过N的概率为d(i)时

\[dp(i) = 1/W * (dp(i + 1)+ dp(i + 2)....+dp(i + W)) \]

那么这就是一个状态转移方程,所以这题可以使用动态规划来解决,由于可以明确的概率是在[K, K + W - 1],因此要推导出dp(0)时,就需要从后往前推动

代码(C++)

class Solution {
public:
    double new21Game(int N, int K, int W) {
        vector<double> dp(K + W, 0) ;
        double t = 0 ;
        for (int i = K + W - 1;i >= K;i--) {
            if (i <= N) dp[i] = 1 ;
            t += dp[i] ;
        }
        for (int i = K - 1;i >= 0;i--) {
            dp [i] = t / W ;
            t = t - dp[i + W] + dp[i] ; 
        }
        return dp[0] ;
    }
};

代码(Java)

class Solution {
    public double new21Game(int N, int K, int W) {
        double[] dp = new double[K + W] ;
        double t = 0 ;
        for (int i = K + W - 1;i >= K;i--) {
            if(i <= N) dp[i] = 1.0 ;
            t += dp[i] ;
        }
        for (int i = K - 1;i >= 0;i--) {
            dp[i] = t / W ;
            t = t - dp[i + W] + dp[i] ;
        }
        return dp[0] ;
    }
}

1641. 统计字典序元音字符串的数目

这题可以类比成完全背包问题求解组合数吧,把N当成背包容量,aeiou当成物品,每个物品的重量都是1,同时要求有序,所以可以当成求解组合数来解决

class Solution {
public:
    int countVowelStrings(int n) {
        vector<int> dp(n + 1, 0) ;
        dp[0] = 1 ;
        for (int i = 1;i <= 5;i++) {
            for (int j = 1;j <= n;j++) {
                dp[j] += dp[j - 1] ;
            }
        }
        return dp[n] ;
    }
};

650. 只有两个键的键盘

官方题解中给出了素数分解的方法,这里使用的是动态规划

先想象一下如何获得5个'A'

那么这些情况合理吗?

所以,获得n个'A'的最少次数,取决于前面的状态,所以可以使用动态规划

假设dp(i, j)等于,当记事本上有i个'A',且复制面板中有j个'A'时所需要的最少操作次数

因此我们可以得出状态转移方程

\[dp(i, j) = dp(i-j,j) + 1\ \ \ \ i/2>j\ \ \ and \ \ \ dp(i-j,j)有效 \]

\[dp(i,i) = min(dp(i,j)) + 1\ \ \ \ i/2 >j\ \ \ and\ \ \ dp(i-j,j)有效 \]

(j < i/2的原因是,i-j < j时,dp(i - j,j)是无效的)

class Solution {
public:
    int dp[1010][1010] ;
    int minSteps(int n) {
        if (n == 1) return 0 ;
        dp[1][1] = 1 ;
        int minn = INT_MAX ;
        for (int i = 2;i <= n;i++) {
            minn = INT_MAX ;
            for (int j = 1;j <= i / 2;j++) {
                if (dp[i - j][j] == 0) continue ;
                dp[i][j] = dp[i - j][j] + 1 ;
                minn = min(minn, dp[i][j]) ; 
            }
            dp[i][i] = minn + 1 ;
        }
        return minn ;
    }
};

1105. 填充书架

本题可以这样想,如果要放下一本新的书,应该怎么放?

可以把这本书放到新的一层,也可以把这本书连同前几本书一起放到新的一层

为什么不到上一层?

因为放到上一层,就相当于把前面的所有书一起放到新的一层,这个情况已经包含在内了

对于一本书的放法所导致的最低高度,取决于前面几本书的摆放,所以本题可以使用动态规划

设dp(i)为放置前i本书的最低高度

class Solution {
public:
    int dp[1010] ;
    int minHeightShelves(vector<vector<int>>& books, int shelf_width) {
        dp[0] = 0 ;
        int h, w ;
        int n = books.size() ;
        for (int i = 1;i <= n;i++) {
            h = 0;
            w = 0 ;
            dp[i] = INT_MAX ;
            for (int j = i;j > 0;j--) {
                h = max(h, books[j - 1][1]) ;
                w += books[j - 1][0] ;
                if (w > shelf_width) {
                    break ;
                }
                dp[i] = min(dp[i], dp[j - 1] + h) ;
            }
        }
        return dp[n] ;
    }
};

992. K 个不同整数的子数组

K个不同整数的子数组的个数可以由(最多K个不同整数的子数组数 - 最多K-1个不同整数的子数组数)获得,因此本题可以转换为求取最多N个不同整数的子数组数

那么求取最多N个不同整数的子数组数可以使用双指针解决:left指针指向子数组左端,right指针指向子数组右端+ 1。哈希表f记录当前子数组每个字符的出现此时,count表示子数组有多少个不同的整数。

子数组是[left, right)这个区间的元素

class Solution {
public:
    int f[20010] ;
    int subarraysWithKDistinct(vector<int>& A, int K) {
        return helper(A, K) - helper(A, K - 1) ;
    }

    int helper(vector<int>& A, int K) {
        int len = A.size() ;
        memset(f, 0, sizeof(f)) ;
        int left = 0 ;
        int right = 0 ;
        int count = 0 ;
        int res = 0 ;
        while (right < len) {
            if (f[A[right]] == 0) {
                count ++ ;
            } 
            f[A[right]] ++ ;
            right ++ ;

            while(count > K) {
                f[A[left]] -- ;
                if (f[A[left]] == 0) {
                    count -- ;
                }
                left ++ ;
            }
            res += right - left ;
        }
        return res ;
    }
};

牛客题霸 反转链表

用pre指代前一个链表节点,用cur指代当前节点,那么链表反转的操作就是:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if (pHead == nullptr) {
            return nullptr ;
        }
        if (pHead->next == nullptr) return pHead ;
        ListNode* pre = nullptr ;
        ListNode* cur = pHead ;
        ListNode* tmp = nullptr ;
        while (cur != nullptr) {
            tmp = cur->next ;
            cur->next = pre ;
            pre = cur ;
            cur = tmp ;
        }
        return pre ;
    }
};

标签:分数,2021.2,cur,int,练习,算法,复制,爱丽丝,dp
来源: https://www.cnblogs.com/Suans/p/14394110.html