算法提升(7):前缀树、树形dp、数组中和最大的子数组及其二维数组推广

题目1

给你一个字符串类型的数组arr,譬如:String[] arr = { "b\\cst", "d\", "a\\d\\e", "a\\b\\c" };
你把这些路径中蕴含的目录结构给画出来,于目录直接列在父目录下面,并比父目录向右进两格,就像这样:

a
  b
    c
  d
    e
b
  cst
d

同一级的需要按字母顺序排列,不能乱。

思路

把所有字符串构建出前缀树,然后对前缀树进行深度优先遍历,注意空格的输出

class Node
{
public:
    Node(string str)
    {
        this->str = str;
    }

public:
    string str;
    map<string, Node*> nextMap;
};

void printProcess(Node* head, int level);

void directoryStructure(vector<string> strArr)
{
    if (strArr.size() == 0)
    {
        return;
    }
    Node *head = new Node("");
    for (auto str : strArr)
    {
        vector<string> paths;
        while (str != "")
        {
            int pos = str.find("\\");
            if (pos == -1)
            {
                paths.push_back(str);
                break;
            }
            paths.push_back(str.substr(0, pos));
            str = str.substr(pos + 2, str.size());
        }
        Node *cur = head;
        for (int i = 0; i < paths.size(); i++)
        {
            if (cur->nextMap.count(paths[i]) == 0)
            {
                cur->nextMap.insert(make_pair(paths[i], new Node(paths[i])));
            }
            cur = cur->nextMap.at(paths[i]);
        }
    }
    printProcess(head, 0);
}

void printProcess(Node *head, int level)
{
    if (head == NULL)
    {
        return;
    }
    if (level != 0) 
    {
        string space = "";
        for (int i = 1; i < level; i++)
        {
            space += "  ";
        }
        cout << space << head->str << endl;
    }
    for (auto next : head->nextMap)
    {
        printProcess(next.second, level + 1);
    }
}

题目2

双向链表节点结构和二叉树节点结构是一样的,如果你把last认为是left,next认为是next的话。给定一个搜索二叉树的头节点head,请转化成一条有序的双向链表,并返回链表的头节点。

思路

树形dp。假设左右子树都已经改成双向链表了,返回左右子树双向链表的头和尾,这时候把左子树的尾接到当前节点上,把右子树的头接到当前节点上,再返回左子树的头作为新的双向链表的头,右子树的尾作为新的双向链表的尾。

class Info
{
public:
    Info(TreeNode* head, TreeNode* end)
    {
        this->head = head;
        this->end = end;
    }
public:
    TreeNode* head;
    TreeNode* end;
};

TreeNode* binaryTreeToLinkedList(TreeNode* head)
{
    if (head == nullptr)
    {
        return nullptr;
    }
    return process(head).head;
}

Info process(TreeNode* head)
{
    if (head == nullptr)
    {
        return Info(nullptr, nullptr);
    }
    Info leftInfo = process(head->left);
    Info rightInfo = process(head->right);
    if (leftInfo.end != nullptr)
    {
        leftInfo.end->right = head;
    }
    head->left = leftInfo.end;
    head->right = rightInfo.head;
    if (rightInfo.head != nullptr)
    {
        rightInfo.head->left = head;
    }
    return Info(leftInfo.head != nullptr ? leftInfo.head : head, rightInfo.end != nullptr ? rightInfo.end : head);
}

题目3

找到一棵二叉树中,最大的搜索二叉子树,返回最大搜索二叉子树的节点个数。

思路

树形dp。

class Info
{
public:
    Info(TreeNode* maxbsthead, int max, int min, int maxbstsize)
    {
        this->maxBSTHead = maxbsthead;
        this->max = max;
        this->min = min;
        this->maxBSTSize = maxbstsize;
    }
public:
    TreeNode* maxBSTHead;
    int max;
    int min;
    int maxBSTSize;
};

Info process(TreeNode* head);

int maxBSTSize(TreeNode* head)
{
    if (head == nullptr)
    {
        return 0;
    }
    Info ans = process(head);
    return ans.maxBSTSize;
}

Info process(TreeNode* head)
{
    if (head == nullptr)
    {
        return Info(nullptr, INT_MIN, INT_MAX, 0);
    }
    Info leftInfo = process(head->left);
    Info rightInfo = process(head->right);
    if (leftInfo.maxBSTHead == head->left && rightInfo.maxBSTHead == head->right)
    {
        if (head->value > leftInfo.max && head->value < rightInfo.min)
        {
            int max = rightInfo.max == INT_MIN ? head->value : rightInfo.max;
            int min = leftInfo.min == INT_MAX ? head->value : leftInfo.min;
            return Info(head, max, min, leftInfo.maxBSTSize + 1 + rightInfo.maxBSTSize);
        }
    }
    return rightInfo.maxBSTSize > leftInfo.maxBSTSize ? rightInfo : leftInfo;
}

题目4

给定一个整型数组arr,数组中的值有正有负,返回连续子数组中最大的累加和
如:[1, 1, -1, -10, 11, 4, -6, 9, 20, -10, -2]
连续子数组中最大的累加和为:11+4+(-6)+9+20=38。

思路

假设答案法,我们假设一段连续子数组为累加和最大的且长度最长的子数组,它的开头位置是i,结尾位置是j。根据假设它具有以下性质:
如果整个arr数组中全都是负数:这个子数组就是整个数组中值最大的那个数字
如果arr数组中存在正数:

  1. 从i位置开始,到i~j之间任意位置的累加和必大于0。因为如果小于0,会使累加和变小,则这一段不会被囊括进累加和最大的子数组,这与我们的假设相违背
  2. i-1位置之前的任意位置到i-1的累加和必小于0。因为如果大于0,则会被囊括进累加和最大的子数组,为什么能不等于0,因为我们假设的是最长的子数组。

根据这两条性质,我们设计以下流程:定义一个变量curSum,初始值都是0。一个变量max,初始值是INT_MIN。开始遍历数组,curSum开始累加当前位置上的数字,每次累加完时与max比较,比max大则把max更新成当前的curSum,如果比max小max就不变。当curSum小于0时,把curSum置为0(先更新max再置为0),继续遍历下一个位置。直到数组遍历完,返回max

int maxSum(vector<int> arr)
{
    if (arr.size() < 1)
    {
        return 0;
    }
    int curSum = 0;
    int max = INT_MIN;
    for (int i = 0; i < arr.size(); i++)
    {
        curSum += arr[i];
        max = curSum > max ? curSum : max;
        curSum = curSum < 0 ? 0 : curSum;
    }
    return max;
}

题目5

给定一个整型矩阵,返回子矩阵的最大累计和。

思路

按行枚举所有连续行的组合,每个组合内把所有行的值都累加起来形成一个列等同于原矩阵的一维数组,对这个一维数组调用题目4的解,记录下来。把记录中最大的值返回即可。

int maxSumMatrix(vector<vector<int>> matrix)
{
    if (matrix.size() == 0 || matrix[0].size() == 0)
    {
        return 0;
    }
    int max = INT_MIN;
    vector<int> curArr(matrix[0].size());
    for (int i = 0; i < matrix.size(); i++)
    {
        for (int j = i; j < matrix.size(); j++)
        {
            if (j == i)
            {
                curArr = matrix[j];
            }
            else
            {
                for (int k = 0; k < matrix[0].size(); k++)
                {
                    curArr[k] += matrix[j][k];
                }
            }
            int cur = maxSum(curArr);    //maxSum函数见题目4的解
            max = cur > max ? cur : max;
        }
    }
    return max;
}
posted @ 2022-08-13 16:35  小肉包i  阅读(25)  评论(0)    收藏  举报