注:这篇主要是一些算法题的解题记录,题目无难度,解答无参考价值。主要目的是让自己多敲一敲键盘,写一些多少需要大脑参与的代码。

2019-08-15

遇到三道算法题,没一道正确解出来的……悲惨。这里先记录一下题目内容和概述,做完了之后再补上解答,争取做到高效 + 少 Bug。

1. 有序循环链表插值

定义循环链表,其中值有序存储,例如 1 -> 3 -> 5 -> 7 (-> 1),现在需要插入一个值在合适的位置,例如插入 6 得到 1 -> 3 -> 5 -> 6 -> 7 (-> 1)

分享一下错误思路:这个题初看不难,于是慌忙写上基本步骤,然后……出错。有几个点没有考虑到:如果插入的值比最后一项大的话?如果插入的值比最后一项小的话?如果插入的值和链表中的值,或者链表中的值相等的话?这些第一次写都没考虑到。

大概是比较正确的解答,如下的 template<typename T> void List<T>::insert(T x) 是主要解答内容。为了练手……这边同时给了基本的数据结构实现。

#include <algorithm>
#include <iostream>
#include <set>
#include <vector>

template<typename T = int>
class ListNode {
public:
    ListNode(T);

    ListNode(const ListNode &) = delete;

    ListNode &operator=(const ListNode &) = delete;

    ListNode(ListNode &&) = delete;

    ListNode &operator=(ListNode &&) = delete;

    static ListNode *make_cycle(std::vector <T>);

    ~ListNode();

    T val;
    ListNode *next;
};

template<typename T = int>
class List {
    template<typename U>
    friend std::ostream &operator<<(std::ostream &os, const List<U> &list);

public:
    List(ListNode<T> *);

    List(const List &) = delete;

    List &operator=(const List &) = delete;

    List(List &&) = delete;

    List &operator=(List &&) = delete;

    void insert(T);

    ~List();

    ListNode<T> *head;
};

template<typename T>
ListNode<T>::ListNode(T x) : val(x), next(nullptr) {}

template<typename T>
ListNode<T> *ListNode<T>::make_cycle(std::vector <T> v) {
    std::sort(v.begin(), v.end());
    ListNode *temp = new ListNode(0);
    ListNode *now = temp;
    for (auto i : v) {
        now->next = new ListNode(i);
        now = now->next;
    }
    ListNode *head = temp->next;
    now->next = head;
    delete temp;
    return head;
}

template<typename T>
ListNode<T>::~ListNode() {}

template<typename T>
List<T>::List(ListNode<T> *head) : head(head) {}

template<typename T>
void List<T>::insert(T x) {
    ListNode<T> *now = this->head;
    if (now == nullptr) return;

    while (now->next != nullptr && now->next != this->head) {
        if (x >= now->val && x <= now->next->val) {
            ListNode<T> *temp = now->next;
            now->next = new ListNode<T>(x);
            now->next->next = temp;
            return;
        }
        now = now->next;
    }

    if (x >= now->val) {
        ListNode<T> *temp = now->next;
        now->next = new ListNode<T>(x);
        now->next->next = temp;
    } else if (x <= now->next->val) {
        ListNode<T> *temp = now->next;
        now->next = new ListNode<T>(x);
        now->next->next = temp;
        this->head = now->next;
    }
}

template<typename T>
List<T>::~List() {
    std::set < ListNode<T> * > s;
    ListNode<T> *now = this->head;
    while (now != nullptr && s.find(now) == s.end()) {
        s.insert(now);
        now = now->next;
    }
    for (auto i : s) delete i;
}

template<typename T>
std::ostream &operator<<(std::ostream &os, const List<T> &list) {
    std::set < ListNode<T> * > s;
    ListNode<T> *now = list.head;
    while (now->next != nullptr && s.find(now->next) == s.end()) {
        s.insert(now);
        os << now->val << " -> ";
        now = now->next;
    }
    os << now->val;
    return os;
}

int main() {
    List<int> cycle(ListNode<int>::make_cycle(std::vector < int > {4, 7, 3, 6}));
    cycle.insert(1);
    cycle.insert(6);
    cycle.insert(5);
    cycle.insert(8);
    cycle.insert(9);
    cycle.insert(9);
    cycle.insert(0);
    cycle.insert(0);
    cycle.insert(3);
    cycle.insert(3);
    cycle.insert(7);
    std::cout << cycle << std::endl;
    return 0;
}

编译,运行 $ valgrind ./a.out ,结果如下。

==29806== Memcheck, a memory error detector
==29806== Copyright (C) 2002-2017, and GNU GPL’d, by Julian Seward et al.
==29806== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==29806== Command: ./a.out
==29806==
0 -> 0 -> 1 -> 3 -> 3 -> 3 -> 4 -> 5 -> 6 -> 6 -> 7 -> 7 -> 8 -> 9 -> 9
==29806==
==29806== HEAP SUMMARY:
==29806== in use at exit: 0 bytes in 0 blocks
==29806== total heap usage: 48 allocs, 48 frees, 75,160 bytes allocated
==29806==
==29806== All heap blocks were freed – no leaks are possible
==29806==
==29806== For counts of detected and suppressed errors, rerun with: -v
==29806== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

正常运行,正常析构,没有内存泄漏。题目不难,做好困难(当然,不犯蠢的话,简单的写出插值函数并不出错不是一件很难的事……)。

2. 线段数组去除被完全覆盖项

定义线段的数据结构,给出数组,去除被完全覆盖的项。例如,{[1, 5], [2, 3], [4, 6]} 去除覆盖项后得到 {[1, 5], [4, 6]}

// TODO:
// 等我想出比 O(n^2) 更好的算法再回来填这里……

3. 固定格式字符串转树

给出字符串,例如 (1 (3 0 8) (5 (9 2 3) 6)),生成一棵树如下。

1 -> 3
       -> 8


  -> 5 -> 9 -> 2
            -> 3

       -> 6

题目没有太大难度,无非是确定边界条件之后递归求解。但是……字符串处理有点复杂,还有确定下一个递归条件的点比较麻烦。大概写了一个简单版本的,不带泛型,并且默认就都有节点(上面题意里有空节点占位符,这里就不写了)。同时参考了来自这里简单的打印函数,视觉效果丰富一些,大致如下。

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <set>
#include <string>
#include <vector>

class TreeNode {
public:
    TreeNode(int);

    TreeNode(const TreeNode &) = delete;

    TreeNode &operator=(const TreeNode &) = delete;

    TreeNode(TreeNode &&) = delete;

    TreeNode &operator=(TreeNode &&) = delete;

    static TreeNode *make_complete(std::vector<int>, int, int);

    static TreeNode *from_string(const std::string &);

    static void collect(std::set<TreeNode *> &, TreeNode *);

    ~TreeNode();

    int val;
    TreeNode *left;
    TreeNode *right;

private:
    static TreeNode *from_vector_string(const std::vector <std::string> &, int,
                                        int);

    static int find_front(const std::vector <std::string> &, int);

    static int find_back(const std::vector <std::string> &, int);
};

class Tree {
    friend std::ostream &operator<<(std::ostream &os, const Tree &root);

public:
    Tree(TreeNode *root);

    Tree(const Tree &) = delete;

    Tree &operator=(const Tree &) = delete;

    Tree(Tree &&) = delete;

    Tree &operator=(Tree &&) = delete;

    ~Tree();

    TreeNode *root;

private:
    static void print(std::ostream &os, std::string prefix, const TreeNode *node,
                        bool left);
};

TreeNode::TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}

TreeNode *TreeNode::make_complete(std::vector<int> v, int m, int n) {
    if (m < n) {
        TreeNode *p = new TreeNode(v[m]);
        p->left = TreeNode::make_complete(v, 2 * m + 1, n);
        p->right = TreeNode::make_complete(v, 2 * m + 2, n);
        return p;
    }
    return nullptr;
}

TreeNode *TreeNode::from_string(const std::string &str) {
    std::vector <std::string> v;
    std::string temp;
    for (auto c : str) {
        if ((c == '(' || c == ')' || c == ' ') && !temp.empty()) {
            v.push_back(temp);
            temp = std::string();
        }

        if (c >= '0' && c <= '9') {
            temp.append(std::string(1, c));
        } else if (c == '(' || c == ')') {
            v.push_back(std::string(1, c));
        }
    }
    TreeNode *head = TreeNode::from_vector_string(v, 0, v.size() - 1);
    for (auto i : v) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return head;
}

TreeNode *TreeNode::from_vector_string(const std::vector <std::string> &v, int m,
                                        int n) {
    TreeNode *root = new TreeNode(std::atoi(v[m + 1].c_str()));
    if (((n - m) == 4) && v[m] == "(" && v[n] == ")") {
        root->left = new TreeNode(std::atoi(v[m + 2].c_str()));
        root->right = new TreeNode(std::atoi(v[m + 3].c_str()));
    } else if (m < n) {
        if (v[m + 2] != "(") {
            root->left = new TreeNode(std::atoi(v[m + 2].c_str()));
        } else {
            int i = TreeNode::find_front(v, m + 2);
            root->left = TreeNode::from_vector_string(v, m + 2, i);
        }

        if (v[n - 1] != ")") {
            root->right = new TreeNode(std::atoi(v[m + 3].c_str()));
        } else {
            int i = TreeNode::find_back(v, n - 1);
            root->right = TreeNode::from_vector_string(v, i, n - 1);
        }
    }
    return root;
}

int TreeNode::find_front(const std::vector <std::string> &v, int front) {
    assert(v[front] == "(");
    int count = 0;
    for (int i = front; i < v.size(); i++) {
        if (v[i] == "(")
            count++;
        else if (v[i] == ")")
            count--;
        else
            continue;

        if (!count) return i;
    }
    return -1;
}

int TreeNode::find_back(const std::vector <std::string> &v, int back) {
    assert(v[back] == ")");
    int count = 0;
    for (int i = back; i >= 0; i--) {
        if (v[i] == ")")
            count++;
        else if (v[i] == "(")
            count--;
        else
            continue;

        if (!count) return i;
    }
    return -1;
}

void TreeNode::collect(std::set<TreeNode *> &s, TreeNode *node) {
    if (node == nullptr || s.find(node) != s.end()) return;
    s.insert(node);
    TreeNode::collect(s, node->left);
    TreeNode::collect(s, node->right);
}

TreeNode::~TreeNode() {}

Tree::Tree(TreeNode *root) : root(root) {}

void Tree::print(std::ostream &os, std::string prefix, const TreeNode *node,
                    bool left) {
    if (node != nullptr) {
        os << prefix;
        os << (left ? "├──" : "└──");
        os << node->val << std::endl;
        Tree::print(os, prefix + (left ? "│ " : " "), node->left, true);
        Tree::print(os, prefix + (left ? "│ " : " "), node->right, false);
    }
}

Tree::~Tree() {
    std::set < TreeNode * > s;
    TreeNode::collect(s, this->root);
    for (auto i : s) delete i;
}

std::ostream &operator<<(std::ostream &os, const Tree &root) {
    Tree::print(os, "", root.root, false);
    return os;
}

int main() {
    // std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 2, 4, 6};
    // Tree tree(TreeNode::make_complete(v, 0, v.size()));
    Tree tree(TreeNode::from_string(
            "(1 (1 (4 8 9) (2 5 5)) (0 (1 4 (3 6 2)) (2 5 5)))"));
    std::cout << tree << std::endl;
    return 0;
}

写这个的主要时间,浪费在用在迭代器寻找匹配括号的位置了……find(), begin(), rbegin(), end(), rend()……用法本身不复杂,但是配上奇怪的想法和与 index 值结合的需要,索性直接手写了。

主函数中被注释的项是用来测试数据结构书写正确性的,下面就是内容了。还是编译、运行 $ valgrind ./a.out

==19132== Memcheck, a memory error detector
==19132== Copyright (C) 2002-2017, and GNU GPL’d, by Julian Seward et al.
==19132== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==19132== Command: ./a.out
==19132==
( 1 ( 1 ( 4 8 9 ) ( 2 5 5 ) ) ( 0 ( 1 4 ( 3 6 2 ) ) ( 2 5 5 ) ) )
└──1
├──1
│ ├──4
│ │ ├──8
│ │ └──9
│ └──2
│ ├──5
│ └──5
└──0
├──1
│ ├──4
│ └──3
│ ├──6
│ └──2
└──2
├──5
└──5

==19132==
==19132== HEAP SUMMARY:
==19132== in use at exit: 0 bytes in 0 blocks
==19132== total heap usage: 74 allocs, 74 frees, 79,788 bytes allocated
==19132==
==19132== All heap blocks were freed – no leaks are possible
==19132==
==19132== For counts of detected and suppressed errors, rerun with: -v
==19132== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

打印完美,没有泄漏,不保证容错了,就先这样吧。

2019-08-20

谁能想到,我已经不会写 01 背包了呢……呵。直接原因是行列处理反了,其他原因就不再多找了,都是借口。

铭记一下这一刻,还是记录一下代码吧。

输入:
n 物品个数, m 背包总重量
第一排数字记录每个物品的重量
第二排数字记录每个物品的价值

5
10
2 2 6 5 4
6 3 5 4 6

输出:
15

#include <algorithm>
#include <cstring>
#include <iostream>
#include <utility>
#include <vector>

int main() {
    int m = 0, n = 0;
    while (std::cin >> n >> m) {
        std::vector<int> weights;
        std::vector<int> values;
        std::vector<std::pair<int, int>> items;
        for (int i = 0; i < n; i++) {
            int tmp = 0;
            std::cin >> tmp;
            weights.push_back(tmp);
        }
        for (int i = 0; i < n; i++) {
            int tmp = 0;
            std::cin >> tmp;
            values.push_back(tmp);
        }
        for (int i = 0; i < n; i++) {
            items.emplace_back(std::make_pair(weights[i], values[i]));
        }
        std::sort(items.begin(), items.end(),
                    [](std::pair<int, int> a, std::pair<int, int> b) {
                        return a.first < b.first;
                    });

        int dp[n + 1][m + 1];
        std::memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (j < items[i - 1].first) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = std::max(dp[i - 1][j], dp[i - 1][j - items[i - 1].first] +
                                                        items[i - 1].second);
                }
            }
        }
        std::cout << dp[n][m] << std::endl;
    }
    return 0;
}

2019-08-27

做算法题哪里有擅长不擅长的,都是刷题刷出来的,谁能不刷题就擅长这些早 TM 上清华北大了。

LeetCode 5 - Longest Palindromic Substring

很久以前写过,现在碰到一下想不出来了,所以上手直接暴力拆解,O(n^3)……找了一下以前的解答,似乎要好一些,放在这里记录一下。

class Solution {
public:
    string longestPalindrome(string s) {
        int table[1001][1001];
        memset(table, sizeof(table), 0);
        int max = 0, left = 0, right = 0;
        for (int j = 0; j < s.size(); j++) {
        for (int i = 0; i < s.size(); i++) {
            bool flag;
            if (i == j)
            flag = true;
            else if (j - i == 1 && s[i] == s[j])
            flag = true;
            else if (j - i > 1 && s[i] == s[j] && table[i + 1][j - 1])
            flag = true;
            else
            flag = false;
            if (flag) {
            table[i][j] = j + 1 - i;
            if (table[i][j] > max) {
                max = table[i][j];
                left = i;
                right = j;
            }
            }
            else
            table[i][j] = 0;
        }
        }
        return s.substr(left, right - left + 1);
    }
};

慢……需要学习动态规划的解法。

2019-09-25

具体的只记得一道了……

LeetCode 100 - Same Tree

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if (p == nullptr && q == nullptr)
            return true;
        else if (p == nullptr || q == nullptr)
            return false;
        else if (p->val != q->val)
            return false;
        else
            return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
};