剑指offer相关代码整理7-8

49 把字符串转换成整数

有一个小细节,如果直接在代码中用整型的最小值0x80000000与res来进行比较,实际上默认为无符号整数。

/*49 把字符串转换成整数
*/
int g_validInput = true;
int MAX = 0x7fffffff;
int MIN = 0x80000000;
int StrToInt(string str) {
    if (str.size() == 0) {
        g_validInput = false;
        return 0;
    }
    int ind = 0;
    int flag = false;
    if (str[ind]<'0' || str[ind]>'9') {
        if (str[ind] == '+')
            ind++;
        else if (str[ind] == '-') {
            flag = true;
            ind++;
        }
        else {
            g_validInput = false;
            return 0;
        }
    }
    long long res = 0;
    for (;ind < str.size();ind++) {
        if (str[ind]<'0' || str[ind]>'9') {
            g_validInput = false;
            return 0;
        }
        res = res * 10 + (str[ind] - '0')*(flag?-1:1);
        if (res > MAX || res < MIN) {
            g_validInput = false;
            return 0;
        }
    }
    return (int)res;
}
int main() {
    int res = StrToInt(string("123"));
    return 0;
}

51 数组中的重复数字

利用容器map会非常简单,也就是一种消耗空间换时间的算法。

另外有一种巧妙的解法是利用了数字的特征——长度为n的数组,数组中所有的数字范围是0-n-1。利用这个特征,可以实现更加方便快捷且不消耗空间的算法。

代码细节还是需要格外注意:

1) 首先要判断参数是否是合理的;

2) 存在数字重复的条件是——numbers[i]==numbers[numbers[i]]。

bool duplicate(int numbers[], int length, int* duplication) {
    if (length <= 0 || numbers == NULL)
        return false;
    for (int i = 0; i < length; i++) {
        if (numbers[i] < 0 || numbers[i] >= length)
            return false;
    }
    for (int i = 0; i < length; i++) {
        while (i != numbers[i]) {
            if (numbers[i] == numbers[numbers[i]]) {
                *duplication = numbers[i];
                return true;
            }else
                swap(numbers[i], numbers[numbers[i]]);
        }
    }
    return false;
}

52 构建乘积数组

53 正则表达式匹配

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。

【这道题,只有看一遍解法之后才能完整正确地写出来。主要是厘清思路不容易。】

bool match(char* str, char* pattern)
{
    if (str == NULL || pattern == NULL)
        return false;
    if (*str=='\0'&&*pattern == '\0')
        return true;
    if (*str != '\0'&&*pattern == '\0')
        return false;
    if (*(pattern + 1) == '*') { //现在一定已经能保证pattern+1一定不出界
        /*需要格外注意这里的条件判断,尤其是在*pattern==.的时候。
        在这里,只有在str!='\0'时,pattern中的'.'字符才是有意义的!*/
        if (*pattern == *str || (*pattern == '.'&&*str != '\0'))
            return match(str, pattern + 2) || match(str + 1, pattern + 2) || match(str + 1, pattern);
        else
            return match(str, pattern + 2);
    }
    if (*pattern == *str || (*pattern == '.'&&*str != '\0'))
        return match(str + 1, pattern + 1);
    return false;
}

54 表示数值的字符串

非常麻烦的一道题目,并且到底对于哪些数字是合理合法的需要辨识清楚。

55 字符流中第一个不重复的字符

56 链表中环的入口

只要知道方法,那么接下来的一切都将是细心与链表的问题。

57 删除链表中重复的结点

58 二叉树的下一个结点——中序遍历的下一个结点

不复杂,代码小心。

TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
    if (pNode == NULL)
        return NULL;
    if (pNode->right == NULL&&pNode->next == NULL)
        return NULL;
    if (pNode->right) {
        TreeLinkNode* tlnode = pNode->right;
        while (tlnode->left) {
            tlnode = tlnode->left;
        }
        return tlnode;
    }
    if (pNode->next->left == pNode)
        return pNode->next;
    if (pNode->next->right == pNode) {
        TreeLinkNode* tlnode = pNode->next;
        while (tlnode->next&&tlnode->next->right == tlnode)
            tlnode = tlnode->next;
        if (tlnode == NULL)
            return NULL;
        else
            return tlnode->next;
    }
    return NULL;
}

59 对称的二叉树

    很漂亮的解法,只要知道方法就会非常简单。

bool funcIST(TreeNode* pNode1, TreeNode* pNode2) {
    if (pNode1 == NULL&&pNode2 == NULL)
        return true;
    if (pNode1 == NULL || pNode2 == NULL)
        return false;
    if (pNode1->val != pNode2->val)
        return false;
    return funcIST(pNode1->left, pNode2->right) && funcIST(pNode1->right, pNode2->left);
}
bool isSymmetrical(TreeNode* pRoot)
{
    return funcIST(pRoot, pRoot);
}

60 把二叉树打印成多行

61 按照之字形顺序打印二叉树

我之前用的一个很好使的方法,非常有通用性——对于每行的结点用vector1来进行存储,根据该vector1,按照题意来打印每行结点。并根据该vector1中的结点用另一个新的vector2来进行存储。

效率不太高,但是思路清晰明了。

 

62 序列化二叉树

这题的关键是在于理解题目到底需要你干嘛。也就是说,需要将二叉树转化为一个序列,要求能够将该序列重新构建成一棵树。

几个注意点:

1) 格外注意typedef char* pchar; 记得每次一旦函数内涉及修改指针,就用这种形式。在作为参数时一定要写成引用的形式。否则每写必错。

2) 实际上很容易写成默认结点的值是一位的整数。并不是如此,因此需要有符号将所有的数字隔开,于是问题会变得稍稍复杂。

3) 另外,这题的代码在构建二叉树用来测试写的代码的时候,可以用一用。格式是调用Deserialize()函数,该函数的作用是将序列转换为二叉树。注意输入的参数是char*类型的序列。该序列举例为char* str = “1,222,#,#,32,42,#,#,#,#”

/*62 序列化二叉树
*/
char* Serialize(TreeNode *root) {
    if (!root) {
        return "#,";
    }
    string str = to_string(root->val);
    str.push_back(',');
    char* left = Serialize(root->left);
    char* right = Serialize(root->right);
    char* res = new char[strlen(left) + strlen(right) + str.size() + 1];
    strcpy(res, str.c_str());
    strcat(res, left);
    strcat(res, right);
    res[strlen(res)] = '\0';
    return res;
}
typedef char* pchar;
TreeNode* Deserialize_tmp(pchar& str) {
    while (*str != '\0' && (*str == ',')) {
        str++;
    }
    if (*str == '\0' || *str == '#') {
        str++;
        return NULL;
    }
    else {
        int val = 0;
        while (*str >= '0'&&*str <= '9') {
            val = val * 10 + *str - '0';
            str++;
        }
        TreeNode* root = new TreeNode(val);
        root->left = Deserialize_tmp(str);
        root->right = Deserialize_tmp(str);
        return root;
    }
}
TreeNode* Deserialize(char *str) {
    TreeNode* root = Deserialize_tmp(str);
    return root;
}

int main() {
    char* str = "1,2,#,#,3,#,#";
    TreeNode* root = Deserialize(str);
    char *res = Serialize(root);
    return 0;
}

63 二叉搜索树的第k个结点

实际上就是中序遍历而已。但是还是折腾好久。

/*63 二叉搜索树的第k个结点
*/
TreeNode* funcKthNode(TreeNode* pRoot, int& k)
{
    TreeNode* resNode = NULL;
    if (pRoot == NULL||k == 0)
        return NULL;
    resNode = funcKthNode(pRoot->left, k);
    if (resNode == NULL) {
        k--;
        if (k == 0)
            resNode = pRoot;
    }
    if (resNode == NULL)
        resNode = funcKthNode(pRoot->right, k);
    return resNode;
}
TreeNode* KthNode(TreeNode* pRoot, int k)
{
    return funcKthNode(pRoot, k);
}

64 数据流中的中位数

思路分析:

1) 数组是最简单的数据容器,如果数组没有排序,可以用partition函数找出数组中的中位数。在没有排序的数组中插入一个数字时间复杂度为O(1),找出中位数的时间复杂度为O(n)。

2) 或者维持数组为排序数组,然后直接从排序数组中找出中位数。

3) 排序的链表(注意定义两个指针指向链表中间的结点),与2)一样。

4) 二叉搜索树可以把插入新数据的平均时间降为O(logn)。为了得到中位数,可以在数据结构中增加一个表示子树结点数目的字段。有了这个字段,可以在平均O(logn)时间得到中位数。

5)4)的改进——平衡的二叉搜索树AVL树。并且可以稍作修改,把AVL的平衡因子从左右子树的高度差改为左右子树结点数目之差。有了这个改动之后,可以用O(logn)时间往AVL树添加新的结点。同时用O(1)时间得到所有结点的中位数。

6)维持最大堆最小堆的方法:为了程序方便,可以使用stl中现成的工具——堆的相关函数:push_heap,pop_heap。以及容器vector。

这是一个非常漂亮的算法。并且由于直接利用了stl中堆的相关工具,因此也十分简洁。最关键的是再次熟悉一下stl中有关于堆的工具。

/*64 数据流中的中位数
*/
#include<functional>
#include<algorithm>
vector<int> minHeap, maxHeap;
void Insert(int num)
{
    if ((minHeap.size() + maxHeap.size()) & 0x1 == 1) {
        /*应当存放入最小堆,但是要判断万一num中的数比最大堆里的数小怎么办。
        一旦发生这种情况,就先安置到最大堆,然后取最大堆里的最大元素放到最小堆。
        */
        if (maxHeap.size() > 0 && maxHeap[0] > num) {
            maxHeap.push_back(num);
            push_heap(maxHeap.begin(), maxHeap.end(), less<int>());
            num = maxHeap[0];
            pop_heap(maxHeap.begin(), maxHeap.end(), less<int>());
            maxHeap.pop_back();
        }
        minHeap.push_back(num);
        push_heap(minHeap.begin(), minHeap.end(), greater<int>());
    }
    else {
        if (minHeap.size() > 0 && minHeap[0] < num) {
            minHeap.push_back(num);
            push_heap(minHeap.begin(), minHeap.end(), greater<int>());
            num = minHeap[0];
            pop_heap(minHeap.begin(), minHeap.end(), greater<int>());
            minHeap.pop_back();
        }
        maxHeap.push_back(num);
        push_heap(maxHeap.begin(), maxHeap.end(), less<int>());
    }
}
double GetMedian()
{
    int sz = minHeap.size() + maxHeap.size();
    if (sz == 0)
        throw exception("No numbers are available!");
    if ((minHeap.size() + maxHeap.size()) & 0x1 == 1)
        return maxHeap[0];
    else
        return double(minHeap[0] + maxHeap[0]) / 2;
}

65 滑动窗口的最大值

题目描述:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}。

实际也不难。只要掌握了deque容器的用法。以及一个小小的技巧,deque中直接存储数组元素下标。

写代码最害怕给我一个内存溢出的错误。很可能怎么也找不到问题老是要怀疑网上的这个编译器是不是坏的。然而实际上,百分之百是代码有问题,并且十有八九是因为条件判断没有做好。——一定要先把不符合条件的输入都过滤掉。

/*65 滑动窗口的最大值
*/
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
    vector<int> res;
    if (num.size() == 0||num.size() < size||size == 0)
        return res;
    deque<int> dq;
    int cnt = 0;
    while (cnt < size) {
        int tmp = num[cnt];
        while (!dq.empty()&&num[dq.back()] <= tmp) {
            dq.pop_back();
        }
        dq.push_back(cnt);
        cnt++;
    }
    res.push_back(num[dq.front()]);
    while (cnt < num.size()) {
        int tmp = num[cnt];
        while (!dq.empty() && num[dq.back()] <= tmp) {
            dq.pop_back();
        }
        dq.push_back(cnt);
        if (dq.front() <= (int)(cnt - size))
            dq.pop_front();
        res.push_back(num[dq.front()]);
        cnt++;
    }
    return res;
}

 

posted on 2017-07-18 16:33  sjqiu  阅读(129)  评论(0编辑  收藏  举报

导航