算法提升(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台),分以下情况进行讨论

  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)步才能满足要求
  2. 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)}步才能满足要求
  3. 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)}步才能满足要求、
  4. 左边少右边多的情况同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;      //目前为止堆内的元素个数
};
posted @ 2022-08-10 09:54  小肉包i  阅读(55)  评论(0)    收藏  举报