牛客剑指offer刷题记录(五)

复杂链表复制

链表的指针域中,除了有指向下一个节点的链表以外,还有一个指向随机节点的指针。

struct ListNode
{
    int val;
    ListNode * next;
    ListNode * random;
};

思路一

常规做法,空间换时间。
先常规的将拷贝的节点用next串起来,遍历一遍原始链表,然后尾插法即可。
在尾插的同时,建立一个由原始节点指针P到拷贝节点指针C的一个map。

再次遍历原始指针,如果指针p指向节点的random域不为NULL,那么通过m[p]->random=m[p->random]来达到赋值random的目的:

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if (nullptr == pHead)
            return nullptr;
        RandomListNode * p = pHead;
        unordered_mapm;
        RandomListNode *newHead = new RandomListNode(0);
        RandomListNode*tail = newHead;
        while (nullptr != p)
        {
            RandomListNode * r = new RandomListNode(p->label);
            //if (nullptr != p->random)
            m[p] = r;
            tail->next = r;
            tail = r;
            p = p->next;
        }
        tail->next = nullptr;
        p = pHead;
        while (nullptr != p)
        {
            if (nullptr != p->random)
            {
                m[p]->random = m[p->random];
            }
            p = p->next;
        }
        return newHead->next;
    }
};

思路二

相当聪明的做法,看了书才能知道,就是把clone的节点接到原节点的后面
例如:
l1->l2->l3
变成
l1->l1’->l2->l2’->l3->l3’->NULL

然后有p->next->random=p->random->next

最后再把这个链表拆成两个链表即可。

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if (nullptr == pHead)
            return nullptr;
        RandomListNode * p = pHead;
        while (nullptr != p)
        {
            RandomListNode*r = new RandomListNode(p->label);
            r->next = p->next;
            p->next = r;
            p = r->next;
        }
        p = pHead;
        while (nullptr != p)
        {
            if (nullptr != p->random)
            {
                p->next->random = p->random->next;
            }
            p = p->next->next;
        }
        RandomListNode * newHead = new RandomListNode(0);//新链表的头结点
        RandomListNode *k = newHead;

        RandomListNode* o = pHead;//这里还需要保证原链表的形状
        p = pHead->next;
        while (nullptr != p)
        {
            o->next = o->next->next;
            k->next = p;
            k = p;
            if (nullptr != p->next)
                p = p->next->next;
            else
                break;
            o = o->next;
        }
        return newHead->next;

    }
};

二叉搜索树与双向链表

将二叉搜索树转成双向链表:
原先指向左子树的指针调整为指向双向链表中前一个节点的的指针pre,原先指向右子树的指针调整为指向双向链表后一个节点的指针next.

由于二叉搜索树中序遍历的序列是有序的,而题目要求双向链表也是有序的,因此,我们以中序的方式进行递归。

这里,我们还需要一个辅助的指针,指向当前已经排好的双向链表的最后一个节点。

所以,递归的顺序是,先排左子树,再排当前节点,再排右子树。

排好以后,list指针需要往回溯,找到双向链表的头结点。

class Solution {
private:
    void inorder(TreeNode *root,TreeNode *&list)
    {
        if(nullptr==root)
            return;
        inorder(root->left,list);
        root->left = list;
        if (nullptr != list)
            list->right = root;
        list = root;
        inorder(root->right,list);
    }
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if (nullptr == pRootOfTree)
            return nullptr;
        TreeNode *list = nullptr;
        inorder(pRootOfTree, list);
        while (nullptr!=list&&nullptr != list->left)
        {
            list = list->left;
        }
        return list;
    }
};

字符串全排列

把字串分成两部分,一部分是字符串的第一个字符,一部分是第一个字符后面所有的字符,将第一个字符分别与后面所有的字符交换:
比如abc,a与b、c分别交换,得到序列bac和cba。

对于子序列,再递归地完成该操作即可。

不过牛客上面有个两个要求:
1.要求字符串出现重复字符,即可能有aac这样的序列
2.要求字符串是字典序的。

对于第2个要求,我们很容易实现,只要将第一个字符后面所有的字符sort一下即可,对于第1个要求,如果出现重复字符,则不执行swap操作,这里需要维护一个visit容器。

class Solution {
private:
    void dfs(vector<string>&result, string str, int start)
    {
        if (start == str.size())
        {
            result.push_back(str);
            //cout << str << endl;
        }
        sort(str.begin() + start, str.end());//保证字典序
        unordered_set<char>visit;//保证重复不交换
        for (int i = start; i < str.size(); ++i)
        {
            if (visit.find(str[i]) == visit.end())
            {
                visit.insert(str[i]);
                swap(str[start], str[i]);
                dfs(result, str, start + 1);
                swap(str[start], str[i]);
            }
        }
    }
public:
    vector<string> Permutation(string str) {
        if (0 == str.size())
            return vector<string>(0);
        vector<string>result;
        dfs(result, str, 0);
        return result;
    }
};

数组中出现次数超过一半的数字

最简单的办法莫过于排序取中间的数,这样时间效率就不高了,另外可以采用hash的方式统计一下,这样用额外的空间换取时间倒是可以将时间复杂度控制在线性。最后有一个很牛逼的算法交筛选法。

详细参考:LeetCode169—MajorityElements

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        int candidates = numbers[0];
        int count = 0;
        for (int i = 0; i < numbers.size(); ++i)
        {
            if (count == 0)
            {
                count = 1;
                candidates = numbers[i];
            }
            else
            {
                count = (candidates == numbers[i]) ? (count + 1) : (count - 1);
            }
        }
        //下面代码排除没有多数派情况
        count = 0;
        for (int i = 0; iif (numbers[i] == candidates)
                count++;
        }
        if (count <= (numbers.size() ) / 2)
            return 0;

        return candidates;
    }
};

最小的k个数

借助容器了。
能想象到,最小的k个数和最大的k个数可以用堆这种结构来保存。这里我用一个优先级队列。事实上红黑树也是可以的,用一个set也是可以的。

优先级队列

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if (k == 0 || input.size() == 0||k>input.size())
            return vector<int>();
        priority_queue<int>q;
        for (int i = 0; i < input.size(); ++i)
        {
            q.push(input[i]);
            if (q.size()>k)
                q.pop();
        }
        vector<int>v;
        v.reserve(q.size());
        while (!q.empty())
        {
            v.push_back(q.top());
            q.pop();
        }
        reverse(v.begin(), v.end());
        return v;
    }
};

有序集合

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if (k == 0 || input.size() == 0||k>input.size())
            return vector<int>();
        set<int>s;
        for (int i = 0; i < input.size(); ++i)
        {
            s.insert(input[i]);
            if (s.size()>k)
            {
                s.erase(std::prev(s.end()));
            }
        }
        vector<int>v;
        v.reserve(s.size());
        for (auto it = s.begin(); it != s.end(); ++it)
        {
            v.push_back(*it);
        }

        return v;
    }
};

第二种思路是用partition函数来解决问题,可以想见,快排的思路是比基准小的在左边,比基准pivot大的再右边,先回顾一下快排和partition函数。

int Partition(vector<int>&v, int p, int r)
{
    int x = v[r];//pivot
    int i = p-1;
    for (int j = p; j < r; ++j)
    {
        if (v[j] <= x)
        {
            swap(v[j], v[++i]);
        }
    }
    swap(v[r], v[i+1]);
    return i +1;
}
void QuickSort(vector<int>&v, int p, int r)
{
    if (p < r)
    {
        int q = Partition(v, p, r);
        QuickSort(v, p, q - 1);
        QuickSort(v, q + 1, r);
    }
}

如果基于数组的第k个数字来调整,使得比第k个数字小的在数组左边,比第k个数字大的都在右边,这样左边的k个数字就是k个最小数字。基于这种思路的代码如下:

int Partition(vector<int>&v, int p, int r)
{
    int x = v[r];//pivot
    int i = p-1;
    for (int j = p; j < r; ++j)
    {
        if (v[j] <= x)
        {
            swap(v[j], v[++i]);
        }
    }
    swap(v[r], v[i+1]);
    return i +1;
}
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if (k == 0 || input.size() == 0||k>input.size())
            return vector<int>();
        int p, r;
        p = 0;
        r = input.size() - 1;
        int i = Partition(input, p, r);
        while (i != k - 1)
        {
            if (i > k - 1)
            {
                r = i - 1;
                i = Partition(input, p, r);
            }
            else
            {
                p = i + 1;
                i = Partition(input, p, r);
            }
        }//end while
        vector<int>res(input.begin(), input.begin() + k);
        return res;
    }
};

你可能感兴趣的:(笔试面试)