前言
统计包含给定前缀的字符串 (字典树)
直接用字典树建模
class Solution {
public int prefixCount(String[] words, String pref) {
Trie t = new Trie();
int l = words.length;
//将words放入字典树中
for(int i = 0;i < l;i++){
String s = words[i];
int sl = s.length();
Trie tmp = t;
for(int j = 0;j < sl;j++){
char c = s.charAt(j);
if(tmp.sons[c - 'a'] == null){
tmp.sons[c - 'a'] = new Trie();
}
tmp = tmp.sons[c - 'a'];
tmp.count += 1;
}
}
//直接在字典树中找指定前缀有多少个串
Trie tmp = t;
int pl = pref.length();
for(int i = 0;i < pl;i++){
char c = pref.charAt(i);
if(tmp.sons[c - 'a'] == null){
return 0;
}
tmp = tmp.sons[c - 'a'];
}
return tmp.count;
}
static class Trie{
int count;
Trie[] sons;
public Trie(){
sons = new Trie[26];
count = 0;
}
}
}
使两字符串互为字母异位词的最少步骤数
做这道题的时候用的做法是直接对两个字符串排序,然后同时遍历两个排序后的字符串,记录不一样的字符的个数,就是所求答案,想法应该没错,可惜超时了。最后没有做出来。下面答案的思路来自评论区:
public int minSteps(String s, String t) {
int count[] = new int[26];
int sL = s.length();
int tL = t.length();
//用数组记录每个字符串各自含各种字母的个数以及差值,相差的部分就是要添加的内容
for(int i = 0;i < sL;i++){
count[s.charAt(i) - 'a'] += 1;
}
for(int i = 0;i < tL;i++){
count[t.charAt(i) - 'a'] -= 1;
}
int res = 0;
for(int i = 0;i < 26;i++){
res += Math.abs(count[i]);
}
return res;
}
完成旅途的最少时间 (贪心 + 二分)
思路来自评论区
二分查找的做法,首先,让完成一趟旅途所需时间最少的车独自完成所有的行程,这种情况下算出来的所需的时间就是完成旅途的最长时间,那么就可以以 0 跟这个最长时间为左右边界进行二分查找最优解
public long minimumTime(int[] time, int totalTrips) {
//使用Arrays.sort()方法进行排序。不过这个排序操作不是必需的,因为我们只需要找到最小值
//但下面的算法时间复杂度为O(nlogn),所以排序的话也不影响整个代码最终的渐进复杂度
Arrays.sort(time);
//二分模板
long left = -1;
//提前转化为long类型,否则会溢出
long right = ((long)time[0] * totalTrips) + 1;
while(left + 1 < right){
long mid = (left + right) >> 1;
long trips = 0L;
//二分出mid后,判断mid时间内能否完成旅程
for(int i : time){
//当遍历到某一辆车完成一趟旅程所需时间大于mid,
//说明它不可能在mid时间内对能完成的旅途数做出贡献,
//那么后面比他大的就更不能了
if(mid < i) break;
//计算mid时间内能完成的旅途数
trips += mid / i;
}
//如果mid时间内能完成的旅途数大于等于totalTrips,说明这个时间mid符合条件
//但它又不一定是最优解,可能比它还小的时间中也能完成totalTrips
//所以往左边查找
if(trips >= totalTrips){
right = mid;
}else{
//如果trips小于totalTrips,说明min时间内不可能完成totalTrips,
//需要更多时间,往右边查找
left = mid;
}
}
//只有到最后不能继续拆分区间了得到的才是最优解
return right;
}
完成比赛的最少时间 (先提取题意信息,再动态规划)
看了评论区的思路,然后自己总结:
- 每个轮胎不能连续使用很多次,因为连续使用的话,每圈所需耗费时间是呈指数增长,如果每圈耗费时间大到比停下来换一个新的同类轮胎 (changeTime + tire[i][0]) 还久的话就应该停止继续使用了。根据数据规模预测所有轮胎中能连续使用的最大圈数为 17 圈
- 计算使用每个轮胎连续跑 i 圈所能耗费的最短时间 minTime[i],这个数组的含义可以进一步抽象成:当要连续跑 i 圈时,在这些轮胎中,选取某一个轮胎,所能耗费的最少时间即为 minTime[i]。在计算 minTime 数组的过程中可以记录以下这些轮胎中最多能连续使用 maxRound 圈 (17 只是上界)
- 状态转移:dp[i] 表示跑到第 i 圈时耗费的最少时间,那么在这 i 圈中,枚举 j (1 <= j <= min(i,maxRound)),表示这 i 圈中的后面 j 圈经过一次换胎后连续使用同一个轮胎,那么就可以得到转移方程:
dp[i] = Math.min(dp[i],dp[i - j] + minTime[j] + changeTime);
public int minimumFinishTime(int[][] tires, int changeTime, int numLaps) {
int[] minTime = new int[18];
Arrays.fill(minTime,Integer.MAX_VALUE);
int maxRound = -1,f,r,time;
for(int[] tire : tires){
f = tire[0];
r = tire[1];
time = f; //time 维护每个轮胎每一圈所需耗时,当time > threshold即达到该轮胎连续使用的最大圈数
int threshold = changeTime + f;
for(int i = 1,sum = f;time <= threshold;i++){
minTime[i] = Math.min(sum,minTime[i]);
time *= r;
sum += time; //sum 维护每个轮胎连续使用 i 圈的总耗时
maxRound = Math.max(maxRound,i); //更新轮胎中能连续使用的最大圈数
}
}
int[] dp = new int[numLaps + 1];
//注意到状态转移方程中,当 j 等于 i ,dp[i] = Math.min(dp[i],dp[0] + minTime[i] + changeTime)
//这表示这i圈都使用同一个轮胎 (当然此时 i小于轮胎中所能连续使用的最大圈数maxRound),那么换胎次数就为0,所以dp[0]应等于 -changeTime
dp[0] = -changeTime;
int jThreshold; //枚举j的最大值应当是 i 跟 maxRound中的较小值
for(int i = 1;i <= numLaps;i++){
dp[i] = Integer.MAX_VALUE; //不能设为 minTime[i],minTime数组中下标最大只能到17,而numLaps可以很大
jThreshold = Math.min(i,maxRound);
for(int j = 1;j <= jThreshold;j++){
dp[i] = Math.min(dp[i],dp[i - j] + minTime[j] + changeTime);
}
}
return dp[numLaps];
}