算法提升(3):洗衣机、各种矩阵打印、见过就会没见过就不会的神仙题、前k个词频最多的字符串、改堆实现流数据打印前k个词频最多的字符串
题目1
假设有n台超级洗衣机放在同一排上。开始的时候,每台洗衣机内可能有一定量的衣服,也可能是空的。
在每一步操作中,你可以选择任意 m (1 <= m <= n) 台洗衣机,与此同时将每台洗衣机的一件衣服送到相邻的一台洗衣机。
给定一个整数数组 machines 代表从左至右每台洗衣机中的衣物数量,请给出能让所有洗衣机中剩下的衣物的数量相等的 最少的操作步数 。如果不能使每台洗衣机中衣物的数量相等,则返回-1 。
示例 1:
输入:machines = [1,0,5]
输出:3
解释:
第一步: 1 0 <-- 5 => 1 1 4
第二步: 1 <-- 1 <-- 4 => 2 1 3
第三步: 2 1 <-- 3 => 2 2 2
示例 2:
输入:machines = [0,2,0]
输出:-1
解释:
不可能让所有三个洗衣机同时剩下相同数量的衣物。
思路
假设衣服的总数量是k个,那么如果m不能被k整除,则返回-1。如果可以被整除,那么m/k就是每台洗衣机需要的数量,每台洗衣机现在实际有的数量可能多也可能少,我们假设现在来到了第i台洗衣机(最前面的洗衣机为第1台),分以下情况进行讨论
- i前面的i-1台洗衣机所需的数量(i-1) * m/k大于它们所拥有的实际数量,差值为a。i后面的n-i台洗衣机所需的数量(n-i) * m/k大于它们所拥有的实际数量,差值为
b。那么此时一定是第i台洗衣机多了abs(a)+abs(b)件衣服,因为每次只能移动一件衣服,所以至少需要abs(a)+abs(b)步才能满足要求 - i前面的i-1台洗衣机所需的数量(i-1) * m/k小于它们所拥有的实际数量,差值为a。i后面的n-i台洗衣机所需的数量(n-i) * m/k小于它们所拥有的实际数量,差值为
b。那么此时一定是第i台洗衣机少了abs(a)+abs(b)件衣服,因为每次可以接收两件衣服,所以至少需要max{abs(a),abs(b)}步才能满足要求 - i前面的i-1台洗衣机所需的数量(i-1) * m/k小于它们所拥有的实际数量,差值为a。i后面的n-i台洗衣机所需的数量(n-i) * m/大于它们所拥有的实际数量,差值为
b。那么此时是左边多了abs(a)件,右边少了abs(b)件衣服,abs(a)与abs(b)哪个大哪个需要的步数就多,此时至少需要max{abs(a),abs(b)}步才能满足要求、 - 左边少右边多的情况同3理
我们总结一下,除了i两边都少衣服的情况是至少需要abs(a)+abs(b)步,其他情况都是至少需要max{abs(a),abs(b)}步。我们从第一台洗衣机开始,每台洗衣机都按照这个规则求出至少需要的步骤,那么步骤数最多的就是瓶颈,如果它被满足,其他位置一定被满足,即最终答案。
int findMinMoves(vector<int>& machines)
{
int sum = 0;
int preSum = 0;
int ans = 0;
for (int i = 0; i < machines.size(); i++)
{
sum += machines[i];
}
if (sum % machines.size() != 0)
{
return -1;
}
int avg = sum / machines.size();
for (int i = 0; i < machines.size(); i++)
{
int frontNeed = preSum - i * avg;
cout << frontNeed << " ";
int behindNeed = sum - preSum - machines[i] - (machines.size() - i - 1) * avg;
cout << behindNeed << " ";
int tips;
if (frontNeed < 0 && behindNeed < 0)
{
tips = abs(frontNeed) + abs(behindNeed);
}
else
{
tips = max(abs(frontNeed), abs(behindNeed));
}
ans = tips > ans ? tips : ans;
preSum += machines[i];
cout << endl;
}
return ans;
}
矩阵的各种打印方法
矩阵的打印不要考虑微观上的怎么上下左右走,要从宏观角度去操作
题目2
用zigzag的方式打印矩阵,比如如下的矩阵
0 1 2 3
4 5 6 7
8 9 10 11
打印顺序为:0,1,4,8,5,2,3,6,9,10,7,11

思路
准备两个指针,初始位置都在(0,0),两个指针同时移动,一个指针往右走,走到不能再往右就往下走,另一个指针往下走,走到不能再往下就往右走,每次打印这条这两个指针组成的斜线,每次打印完改变下一次打印的方向。
void printDiagonal(vector<vector<int>> matrix, int rdR, int rdC, int drR, int drC, bool fromUp);
void zigzag(vector<vector<int>> matrix)
{
if (matrix.size() == 0 || matrix[0].size() == 0)
{
return;
}
int rdR = 0;
int rdC = 0;
int drR = 0;
int drC = 0;
int row = matrix.size();
int col = matrix[0].size();
bool fromUp = false; //控制打印方向
while (rdR < row)
{
printDiagonal(matrix, rdR, rdC, drR, drC, fromUp);
rdR += rdC == col - 1 ? 1 : 0;
rdC += rdC == col - 1 ? 0 : 1;
drC += drR == row - 1 ? 1 : 0;
drR += drR == row - 1 ? 0 : 1;
fromUp = !fromUp; //每次打印完方向改变
}
}
void printDiagonal(vector<vector<int>> matrix, int rdR, int rdC, int drR, int drC, bool fromUp)
{
if (fromUp) //从右上角到左下角打印
{
while (rdR != drR + 1)
{
cout << matrix[rdR++][rdC--] << " ";
}
}
else
{
while (drR != rdR - 1) //从左下角到右上角打印
{
cout << matrix[drR--][drC++] << " ";
}
}
}
题目3
用螺旋的方式打印矩阵,比如如下的矩阵
0 1 2 3
4 5 6 7
8 9 10 11
打印顺序为:0,1,2,3,7,11,10,9,8,4,5,6
思路
分层打印,每次打印一圈。准备两个指针,一个初始位置在矩阵左上角,一个在右下角,两个指针确定一圈,然后打印这一圈,打印完两个指针往内层移动,左上角的指针行列都++,右下角的指针行列都--,直到右下角的指针行或列大于左上角的指针,停止打印。
void printCircle(vector<vector<int>> matrix, int upperLeftR, int upperLeftC, int lowerRightR, int lowerRightC);
void spiralOrderPrint(vector<vector<int>> matrix)
{
if (matrix.size() == 0 || matrix[0].size() == 0)
{
return;
}
int row = matrix.size();
int col = matrix[0].size();
int upperLeftR = 0;
int upperLeftC = 0;
int lowerRightR = row - 1;
int lowerRightC = col - 1;
while (upperLeftR <= lowerRightR && upperLeftC <= lowerRightC)
{
printCircle(matrix, upperLeftR++, upperLeftC++, lowerRightR--, lowerRightC--);
}
}
void printCircle(vector<vector<int>> matrix, int upperLeftR, int upperLeftC, int lowerRightR, int lowerRightC)
{
if (upperLeftR == lowerRightR) //矩阵只有一行时,打印这一行
{
for (int i = upperLeftC; i <= lowerRightC; i++)
{
cout << matrix[upperLeftR][i] << " ";
}
}
else if (upperLeftC == lowerRightC) //矩阵只有一列时,打印这一列
{
for (int i = upperLeftR; i <= lowerRightR; i++)
{
cout << matrix[i][lowerRightC] << " ";
}
}
else
{
for (int i = upperLeftC; i < lowerRightC; i++)
{
cout << matrix[upperLeftR][i] << " ";
}
for (int i = upperLeftR; i < lowerRightR; i++)
{
cout << matrix[i][lowerRightC] << " ";
}
for (int i = lowerRightC; i > upperLeftC; i--)
{
cout << matrix[lowerRightR][i] << " ";
}
for (int i = lowerRightR; i > upperLeftR; i--)
{
cout << matrix[i][upperLeftC] << " ";
}
}
}
题目4
给定一个正方形矩阵,只用有限几个变量,实现矩阵中每个位置的数顺时针转动90度,比如如下的矩阵
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
矩阵应该被调整为:
12 8 4 0
13 9 5 1
14 10 6 2
15 11 7 3
思路
与上面螺旋打印的思路类似,也是分层转动,每次完成一个圈的换位。准备两个指针,一个初始位置在矩阵左上角,一个在右下角,两个指针确定一圈,在当前圈时,给圈内的数字分组,按照下面的分组方式四个一组(形状相同的为一组),每次交换一组内的位置,所有组都交换完成后两个指针往内层移动,左上角的指针行列都++,右下角的指针行列都--,直到右下角的指针行或列大于左上角的指针,停止。
,每次转动一组
void rotateCircle(vector<vector<int>> &matrix, int upperLeftR, int upperLeftC, int lowerRightR, int lowerRightC);
void rotateMatrix(vector<vector<int>> &matrix)
{
if (matrix.size() == 0 || matrix[0].size() == 0)
{
return;
}
int row = matrix.size();
int col = matrix[0].size();
int upperLeftR = 0;
int upperLeftC = 0;
int lowerRightR = row - 1;
int lowerRightC = col - 1;
while (upperLeftR < lowerRightR)
{
rotateCircle(matrix, upperLeftR++, upperLeftC++, lowerRightR--, lowerRightC--);
}
}
void rotateCircle(vector<vector<int>>& matrix, int upperLeftR, int upperLeftC, int lowerRightR, int lowerRightC)
{
int groups = lowerRightC - upperLeftC; //一共分多少组
int temp = 0;
for (int i = 0; i < groups; i++)
{
temp = matrix[lowerRightR - i][upperLeftC];
matrix[lowerRightR - i][upperLeftC] = matrix[lowerRightR][lowerRightC - i];
matrix[lowerRightR][lowerRightC - i] = matrix[upperLeftR + i][lowerRightC];
matrix[upperLeftR + i][lowerRightC] = matrix[upperLeftR][upperLeftC + i];
matrix[upperLeftR][upperLeftC + i] = temp;
}
}
题目5
假设s和m初始化,s = "a"; m = s;
再定义两种操作,第一种操作:
m=s;
s =s+s;
第二种操作:
s =s+m;
求最小的操作步骤数,可以将s拼接到长度等于n
思路
当n为质数时,不能在第一步之外调用操作一(当n为2时调用一次操作一或二就结束了,不存在第二步,所以不讨论n为2的情况),因为一旦在第一步之外调用操作一,假设调用之前s=x个a(x≠1),调用操作一后s=2x个a,m=x个a,那么当前得到的s与m就会拥有x这个相同的因子,以后不管怎么调用都拼不成质数了,调用步骤一就会导致s有2这个因子,调用步骤二会导致s有x这个因子。而第一步调用操作一也不会让总步骤数变少,所以得出结论,当n为质数时只能调用步骤二,最少的步骤数就是n-1。
当n不是质数时,那么它就是合数(1既不是质数也不是合数,但是s开始就有1个a,步骤数是0,所以忽略1),任何合数都可以被分解为质数相乘的形式,假设n = X*Y*Z(X、Y、Z都是质数),s变为X需要X-1步,把X看作是开始的默认a,怎么把它变成Y,同样需要Y-1步,同理变成Z需要Z-1步,所以总体需要X+Y+Y-3步,即把合数分解质因数后,最少步骤数就是质数因子的和再减去质数因子的个数。
bool isPrim(int n);
vector<int> getPrimSumAndNum(int n);
int minOpts(int n)
{
if (n < 1)
{
return 0;
}
if (isPrim(n))
{
return n - 1;
}
vector<int> SumAndNum = getPrimSumAndNum(n);
return SumAndNum[0] - SumAndNum[1];
}
bool isPrim(int n)
{
int max = sqrt(n);
for (int i = 0; i <= max; i++)
{
if (n % i == 0)
{
return false;
}
}
return true;
}
vector<int> getPrimSumAndNum(int n)
{
int sum = 0;
int count = 0;
for (int i = 2; i <= n; i++)
{
while (n % i == 0)
{
sum += i;
count++;
n /= i;
}
}
return { sum,count };
}
题目6
给定一个字符串类型的数组arr,求其中出现次数最多(词频数最多)的前K个字符串
思路
准备一个哈希表,key存放字符串,value存放这个字符串出现的次数,准备一个容量为k的小根堆,用词频大小维护,遍历哈希表,小根堆没满之前入小根堆,满了之后看当前的字符串的value是否大于小根堆堆顶的value,是则小根堆顶的元素弹出,当前字符串进小根堆,直到遍历完,把小根堆里的k个字符串弹出就是出现次数最多(词频数最多)的前K个字符串。
class StrFreq //定义一个类用来把字符串和它的词频同时存放进堆
{
public:
StrFreq(string str, int freq)
{
this->str = str;
this->freq = freq;
}
public:
string str;
int freq;
};
class StrFreCompare //定义小根堆内的比较
{
public:
bool operator()(StrFreq s1, StrFreq s2)
{
return s1.freq > s2.freq;
}
};
vector<string> topkString(vector<string> strArr, int k)
{
if (strArr.size() == 0 || k < 1)
{
return {};
}
unordered_map<string, int> freqMap;
for (int i = 0; i < strArr.size(); i++)
{
if (freqMap.count(strArr[i]) == 0)
{
freqMap.insert(make_pair(strArr[i], 1));
}
else
{
freqMap.at(strArr[i])++;
}
}
priority_queue<StrFreq, vector<StrFreq>, StrFreCompare> heap; //小根堆
int size = 0;
for (auto str : freqMap)
{
if (size < k)
{
StrFreq temp(str.first, str.second);
heap.push(temp);
size++;
}
else if (str.second > heap.top().freq)
{
heap.pop();
StrFreq temp(str.first, str.second);
heap.push(temp);
}
}
vector<string> ans;
while (!heap.empty())
{
ans.push_back(heap.top().str);
heap.pop();
}
return ans;
}
题目7
上一题的进阶,用户随时给字符串,随时打印次数最多的k个
思路
自己改堆,添加字符串的时候根据情况调整堆内结构
具体流程设计见https://www.bilibili.com/video/BV13g41157hK?p=23&vd_source=77d06bb648c4cce91c6939baa0595bcd P23 01:35:50
class StrNode
{
public:
StrNode(string str, int times)
{
this->str = str;
this->times = times;
}
public:
string str;
int times;
};
class TopKRecord
{
public:
TopKRecord(int k)
{
heap.resize(k);
heapSize = k;
index = 0;
}
~TopKRecord()
{
for (auto it : strNodeMap)
{
StrNode* temp = it.second;
delete(temp);
temp = nullptr;
}
}
public:
void add(string str)
{
StrNode* curNode = nullptr;
int preIndex = -1;
if (strNodeMap.count(str) == 0)
{
curNode = new StrNode(str, 1);
strNodeMap.insert(make_pair(str, curNode));
nodeIndexMap.insert(make_pair(curNode, -1));
}
else
{
curNode = strNodeMap.at(str);
curNode->times++;
preIndex = nodeIndexMap.at(curNode);
}
if (preIndex == -1)
{
if (index == heapSize)
{
if (heap[0]->times < curNode->times)
{
nodeIndexMap.at(heap[0]) = -1;
heap[0] = curNode;
nodeIndexMap.at(curNode) = 0;
heapify(0, index);
}
}
else
{
heap[index] = curNode;
nodeIndexMap.at(curNode) = index;
heapInsert(index++);
}
}
else
{
heapify(preIndex, index);
}
}
void printTopK()
{
for (int i = 0; i < index; i++)
{
cout << "Str:" << heap[i]->str << " Times:" << heap[i]->times << endl;
}
}
private:
void heapify(int index, int curSize)
{
int lChild = 2 * index + 1;
int rChild = 2 * index + 2;
int minIndex = index;
while (lChild < curSize)
{
if (heap[index]->times > heap[lChild]->times)
{
minIndex = lChild;
}
if (rChild < curSize && heap[rChild]->times < heap[lChild]->times)
{
minIndex = rChild;
}
if (index != minIndex)
{
swap(minIndex, index);
}
else
{
break;
}
index = minIndex;
lChild = 2 * index + 1;
rChild = 2 * index + 2;
}
}
void heapInsert(int index)
{
while (index - 1 > 0 && heap[(index - 1) / 2]->times > heap[index]->times)
{
swap((index - 1) / 2, index);
index = (index - 1) / 2;
}
}
void swap(int index1, int index2)
{
nodeIndexMap.at(heap[index1]) = index2;
nodeIndexMap.at(heap[index2]) = index1;
StrNode* temp = heap[index1];
heap[index1] = heap[index2];
heap[index2] = temp;
temp = nullptr;
}
private:
unordered_map<string, StrNode*> strNodeMap;
unordered_map<StrNode*, int> nodeIndexMap;
vector<StrNode*> heap;
int heapSize; //堆的大小,为k
int index; //目前为止堆内的元素个数
};
浙公网安备 33010602011771号